Claude Code Transcript Sync

Central repository for Claude Code session transcripts from multiple machines

Setup Instructions

1 Set environment variables

Add these to your shell profile (~/.bashrc, ~/.zshrc, etc.):

# Claude Sync configuration
export CLAUDE_SYNC_URL="https://claude.cee.wtf"
export CLAUDE_SYNC_KEY="your-api-key-here"
export CLAUDE_MACHINE_ID="$(hostname)"  # or a custom name

Then reload your shell: source ~/.bashrc (or restart your terminal)

2 Create the hook script

Save this to ~/.claude/hooks/sync-session.sh:

#!/bin/bash
# Claude Code session sync hook
# Uploads transcript to central server on session end and before compacts

: "${CLAUDE_SYNC_URL:=https://claude.cee.wtf}"
: "${CLAUDE_SYNC_KEY:=}"
: "${CLAUDE_MACHINE_ID:=$(hostname)}"

# Skip if no API key configured
[ -z "$CLAUDE_SYNC_KEY" ] && exit 0

# Read hook input from stdin
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')

# Validate required fields
[ -z "$SESSION_ID" ] || [ -z "$TRANSCRIPT_PATH" ] && exit 0

# Expand ~ in path
TRANSCRIPT_PATH="${TRANSCRIPT_PATH/#\~/$HOME}"
[ -f "$TRANSCRIPT_PATH" ] || exit 0

# Upload in background (don't block session exit)
curl -s -X POST "$CLAUDE_SYNC_URL/api/sessions" \
    -H "X-API-Key: $CLAUDE_SYNC_KEY" \
    -F "machine_id=$CLAUDE_MACHINE_ID" \
    -F "session_id=$SESSION_ID" \
    -F "transcript=@$TRANSCRIPT_PATH" \
    --max-time 10 >/dev/null 2>&1 &

exit 0

Make it executable:

mkdir -p ~/.claude/hooks
chmod +x ~/.claude/hooks/sync-session.sh
3 Configure Claude Code hooks

Edit ~/.claude/settings.json to add the hooks:

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [{
          "type": "command",
          "command": "$HOME/.claude/hooks/sync-session.sh"
        }]
      }
    ],
    "PreCompact": [
      {
        "hooks": [{
          "type": "command",
          "command": "$HOME/.claude/hooks/sync-session.sh"
        }]
      }
    ]
  }
}

Why PreCompact? Long sessions compact multiple times. Uploading on each compact captures intermediate states, so if a session crashes you still have the last pre-compact snapshot. Same session_id overwrites previous upload.

4 Verify setup

Test the upload manually:

# Create a test transcript
echo '{"test":true}' > /tmp/test.jsonl

# Upload it
curl -X POST https://claude.cee.wtf/api/sessions \
    -H "X-API-Key: $CLAUDE_SYNC_KEY" \
    -F "machine_id=test" \
    -F "session_id=test-$(date +%s)" \
    -F "transcript=@/tmp/test.jsonl"

# List sessions to confirm (auth required)
curl -H "X-API-Key: $CLAUDE_SYNC_KEY" \
    https://claude.cee.wtf/api/sessions
5 Upload previous sessions (optional)

The hook only uploads sessions when they end. To bulk upload existing sessions from ~/.claude/projects/:

# Upload all existing sessions
for file in ~/.claude/projects/*/*.jsonl; do
    [ -f "$file" ] || continue
    session_id=$(basename "$file" .jsonl)
    echo "Uploading $session_id..."
    curl -s -X POST "$CLAUDE_SYNC_URL/api/sessions" \
        -H "X-API-Key: $CLAUDE_SYNC_KEY" \
        -F "machine_id=$CLAUDE_MACHINE_ID" \
        -F "session_id=$session_id" \
        -F "transcript=@$file"
done

This is only needed once per machine to backfill historical sessions. Future sessions will sync automatically.

API Reference

Authentication: All API endpoints (except / and /api/healthz) require the X-API-Key header. Do NOT use Bearer authentication - use the header directly: X-API-Key: your-key

Method Endpoint Description
POST /api/sessions Upload transcript
GET /api/sessions List sessions (?machine_id=xxx&limit=100)
GET /api/sessions/{id}/transcript Download raw JSONL
GET /api/healthz Health check (no auth required)

Upload Request

POST /api/sessions
Content-Type: multipart/form-data
X-API-Key: your-api-key

machine_id: laptop-home
session_id: abc123-def456
transcript: (file)

Response

{
  "status": "ok",
  "session_id": "abc123-def456",
  "machine_id": "laptop-home",
  "file_size": 12345
}