Skip to content

Instantly share code, notes, and snippets.

@caryp
Last active April 8, 2026 02:51
Show Gist options
  • Select an option

  • Save caryp/63ad093976df06bf7089a30bb0d4bf97 to your computer and use it in GitHub Desktop.

Select an option

Save caryp/63ad093976df06bf7089a30bb0d4bf97 to your computer and use it in GitHub Desktop.
Proposal: Measuring AI-Generated Code Contributions over HTTP

Claude Code Real-Time Edit Tracking

Real-time tracking of AI code edits using Claude Code's PostToolUse hooks.

What it tracks: Lines of Codes modified by Claude Code. Including: File path, tool type, timestamp, git context (branch, commit, repo, author), diff stats (lines added/removed/changed), session and tool use IDs.

What it doesn't track: It does not record the lines of code actually merged into your code base. However, it does minimize impact to developers current workflows.

How it works: By tapping into Claude Code's PostToolUse Hook it posts the metadata to an http endpoint specified by the CLAUDE_EDITS_ENDPOINT environment variable.

NOTE: Experimental example code. Not reviewed for production use. YMMV.

Setup

1. Copy the script (see full script at bottom) to .claude/hooks/capture-edits.sh and make it executable:

chmod +x .claude/hooks/capture-edits.sh

2. Add to your .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/capture-edits.sh"
          }
        ]
      }
    ]
  }
}

3. Configure environment variables in ~/.bashrc or ~/.zshrc:

# HTTP endpoint (default: postbin test URL)
export CLAUDE_EDITS_ENDPOINT=https://your-analytics.com/api/edits

# Enable local logging (default: false)
export CLAUDE_EDITS_LOG_LOCAL=true

Requirements

  • Git repository, Claude Code, jq, curl

Example Payload

{
  "event": "ai_edit",
  "timestamp": "2026-04-03T18:30:45Z",
  "tool": "Edit",
  "file_path": "/absolute/path/to/file.ts",
  "session_id": "uuid",
  "tool_use_id": "toolu_01ABC123",
  "git_branch": "feature/my-feature",
  "git_commit": "abc123...",
  "git_repo": "https://gitlab.com/org/repo.git",
  "git_author_email": "user@example.com",
  "lines_added": 7,
  "lines_removed": 2,
  "lines_changed": 5
}

Troubleshooting

# Verify hook is configured
jq '.hooks.PostToolUse' .claude/settings.json

# View HTTP errors
tail .git/claude-ai/edits-errors.log

# View local edits (if enabled)
tail .git/claude-ai/edits.jsonl | jq .

Full Script: capture-edits.sh

#!/usr/bin/env bash
set -euo pipefail

# Read hook payload from stdin
PAYLOAD=$(cat)

# Extract fields using jq
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name')
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.tool_input.file_path')
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id')
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id')
CWD=$(echo "$PAYLOAD" | jq -r '.cwd')

# Get git context (with fallbacks)
GIT_BRANCH=$(git -C "$CWD" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
GIT_COMMIT=$(git -C "$CWD" rev-parse HEAD 2>/dev/null || echo "unknown")
REPO_URL=$(git -C "$CWD" config --get remote.origin.url 2>/dev/null || echo "unknown")
GIT_AUTHOR_EMAIL=$(git -C "$CWD" config --get user.email 2>/dev/null || echo "unknown")

# Calculate change statistics
if [ "$TOOL_NAME" = "Edit" ]; then
  OLD_STRING=$(echo "$PAYLOAD" | jq -r '.tool_input.old_string')
  NEW_STRING=$(echo "$PAYLOAD" | jq -r '.tool_input.new_string')
  OLD_LINES=$(echo "$OLD_STRING" | wc -l | tr -d ' ')
  NEW_LINES=$(echo "$NEW_STRING" | wc -l | tr -d ' ')
  LINES_ADDED=$NEW_LINES
  LINES_REMOVED=$OLD_LINES
  LINES_CHANGED=$((NEW_LINES - OLD_LINES))
else
  # Write tool - use git diff if file is tracked
  if [ -f "$FILE_PATH" ] && git -C "$CWD" ls-files --error-unmatch "$FILE_PATH" 2>/dev/null; then
    DIFF_STATS=$(git -C "$CWD" diff --numstat HEAD "$FILE_PATH" 2>/dev/null | head -1)
    if [ -n "$DIFF_STATS" ]; then
      LINES_ADDED=$(echo "$DIFF_STATS" | awk '{print $1}')
      LINES_REMOVED=$(echo "$DIFF_STATS" | awk '{print $2}')
      # Handle case where git diff returns "-" for binary files
      [ "$LINES_ADDED" = "-" ] && LINES_ADDED=0
      [ "$LINES_REMOVED" = "-" ] && LINES_REMOVED=0
    else
      LINES_ADDED=0
      LINES_REMOVED=0
    fi
  else
    # New file, count total lines
    LINES_ADDED=$(wc -l < "$FILE_PATH" 2>/dev/null || echo 0)
    LINES_REMOVED=0
  fi
  LINES_CHANGED=$((LINES_ADDED - LINES_REMOVED))
fi

# Build HTTP payload (compact JSON for JSONL format, flattened structure)
HTTP_PAYLOAD=$(jq -nc \
  --arg event "ai_edit" \
  --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --arg tool "$TOOL_NAME" \
  --arg file "$FILE_PATH" \
  --arg session "$SESSION_ID" \
  --arg tool_use_id "$TOOL_USE_ID" \
  --arg git_branch "$GIT_BRANCH" \
  --arg git_commit "$GIT_COMMIT" \
  --arg git_repo "$REPO_URL" \
  --arg git_author_email "$GIT_AUTHOR_EMAIL" \
  --argjson lines_added "$LINES_ADDED" \
  --argjson lines_removed "$LINES_REMOVED" \
  --argjson lines_changed "$LINES_CHANGED" \
  '{
    event: $event,
    timestamp: $timestamp,
    tool: $tool,
    file_path: $file,
    session_id: $session,
    tool_use_id: $tool_use_id,
    git_branch: $git_branch,
    git_commit: $git_commit,
    git_repo: $git_repo,
    git_author_email: $git_author_email,
    lines_added: $lines_added,
    lines_removed: $lines_removed,
    lines_changed: $lines_changed
  }')

# Get endpoint from env var or use default
ENDPOINT="${CLAUDE_EDITS_ENDPOINT:-https://www.postb.in/1775266913389-6062681842595}"

# Check if local logging is enabled (default: false)
LOG_LOCAL="${CLAUDE_EDITS_LOG_LOCAL:-false}"

# Ensure state directory exists (needed for error log)
REPO_ROOT=$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null || echo "$CWD")
STATE_DIR="$REPO_ROOT/.git/claude-ai"
mkdir -p "$STATE_DIR"

ERROR_LOG="$STATE_DIR/edits-errors.log"
LOG_FILE="$STATE_DIR/edits.jsonl"

# Send to HTTP endpoint (non-blocking, with timeout)
# Background execution to prevent blocking Claude Code
(
  curl -X POST "$ENDPOINT" \
    -H "Content-Type: application/json" \
    -d "$HTTP_PAYLOAD" \
    --max-time 5 \
    --silent \
    --fail \
    2>> "$ERROR_LOG" || true
) &

# Log locally if enabled
if [ "$LOG_LOCAL" = "true" ]; then
  echo "$HTTP_PAYLOAD" >> "$LOG_FILE"
fi

# Always exit successfully (never block Claude Code)
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment