Claude Session Registry

Full conversation backup — sync on every push

Quick Start — One Paste

Register on the home page to get your API key, then paste this into any terminal. It installs the sync script, configures Claude Code to auto-backup after every session, and immediately syncs all your existing sessions.

bash <<'SETUP'
# ── Claude Session Registry: one-paste setup ──
# Replace YOUR_API_KEY below with your actual key from repo.ittlh.dev

CSR_API_KEY="${CSR_API_KEY:-YOUR_API_KEY}"
CSR_URL="${CSR_URL:-https://repo.ittlh.dev}"

if [ "$CSR_API_KEY" = "YOUR_API_KEY" ]; then
  echo "ERROR: Set CSR_API_KEY first."
  echo "  export CSR_API_KEY=your-key-here"
  echo "Then re-run this script."
  exit 1
fi

# 1) Persist env vars
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
  [ -f "$rc" ] || continue
  grep -q CSR_API_KEY "$rc" 2>/dev/null && continue
  printf '\n# Claude Session Registry\nexport CSR_API_KEY="%s"\nexport CSR_URL="%s"\n' "$CSR_API_KEY" "$CSR_URL" >> "$rc"
  echo "✓ Added env vars to $rc"
done

# 2) Install sync script
mkdir -p "$HOME/.local/bin"
cat > "$HOME/.local/bin/claude-session-sync" <<'SYNC'
#!/usr/bin/env bash
set -euo pipefail
API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"
[ -d "$CLAUDE_DIR" ] || { echo "No Claude projects at $CLAUDE_DIR"; exit 1; }
synced=0; failed=0
for project_dir in "$CLAUDE_DIR"/*/; do
  [ -d "$project_dir" ] || continue
  project_name=$(basename "$project_dir")
  working_dir=$(echo "$project_name" | sed 's|--|/|g; s|^/||')
  for session_file in "$project_dir"*.jsonl; do
    [ -f "$session_file" ] || continue
    session_id=$(basename "$session_file" .jsonl)
    echo "$session_id" | grep -qE '^[0-9a-f]{8}-' || continue
    http_code=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
      "${BASE_URL}/api/sessions/conversation/${session_id}" \
      -H "X-API-Key: $API_KEY" \
      -H "X-Working-Directory: $working_dir" \
      -H "X-Project-Name: $project_name" \
      -H "Content-Type: application/x-ndjson" \
      --data-binary "@${session_file}")
    if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
      synced=$((synced + 1))
    else
      failed=$((failed + 1))
      echo "  FAILED: ${project_name}/${session_id} (HTTP $http_code)"
    fi
  done
done
echo "✓ Sync complete: $synced ok, $failed failed."
SYNC
chmod +x "$HOME/.local/bin/claude-session-sync"
echo "✓ Installed sync script to ~/.local/bin/claude-session-sync"

# 3) Add ~/.local/bin to PATH if not already there
case ":$PATH:" in
  *":$HOME/.local/bin:"*) ;;
  *) for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
       [ -f "$rc" ] || continue
       grep -q '\.local/bin' "$rc" 2>/dev/null && continue
       echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$rc"
     done
     export PATH="$HOME/.local/bin:$PATH"
     echo "✓ Added ~/.local/bin to PATH" ;;
esac

# 4) Configure Claude Code hook (auto-sync after every session)
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
mkdir -p "$HOME/.claude"
HOOK='{"hooks":[{"type":"command","command":"claude-session-sync 2>/dev/null &"}]}'
if [ -f "$CLAUDE_SETTINGS" ]; then
  if ! grep -q 'claude-session-sync' "$CLAUDE_SETTINGS" 2>/dev/null; then
    jq --argjson hook "$HOOK" '.hooks.Stop = (.hooks.Stop // []) + [$hook]' "$CLAUDE_SETTINGS" > "${CLAUDE_SETTINGS}.tmp"
    mv "${CLAUDE_SETTINGS}.tmp" "$CLAUDE_SETTINGS"
    echo "✓ Added sync hook to Claude Code settings"
  else
    echo "✓ Sync hook already configured"
  fi
else
  printf '{"hooks":{"Stop":[%s]}}\n' "$HOOK" > "$CLAUDE_SETTINGS"
  echo "✓ Created Claude Code settings with sync hook"
fi

# 5) Run initial sync of ALL existing sessions
echo ""
echo "Running initial sync of all existing sessions..."
claude-session-sync

echo ""
echo "═══════════════════════════════════════════════"
echo " ✓ Setup complete!"
echo " All existing sessions are backed up."
echo " New sessions will auto-sync when they end."
echo " Dashboard: https://repo.ittlh.dev"
echo "═══════════════════════════════════════════════"
SETUP

What this does: Installs the sync script, adds env vars to your shell, configures a Claude Code hook so every session auto-syncs when it ends, and immediately backs up all existing sessions (full conversation JSONL files, not just metadata).


Manual Setup — Step by Step

If you prefer to set things up piece by piece, follow the steps below.

1. Get Your API Key

Register on the home page to get an API key, then set these env vars:

export CSR_API_KEY="your-api-key-here"
export CSR_URL="https://repo.ittlh.dev"

Add to ~/.bashrc / ~/.zshrc / $PROFILE so they persist.

2. Install the Sync Script

Save as ~/.local/bin/claude-session-sync and chmod +x it. This backs up full conversation history (JSONL files), not just metadata.

#!/usr/bin/env bash
# claude-session-sync — back up Claude Code sessions + conversations
# Uploads session metadata AND full conversation JSONL files to the registry.
set -euo pipefail

API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"

if [ ! -d "$CLAUDE_DIR" ]; then
  echo "No Claude projects directory at $CLAUDE_DIR"
  exit 1
fi

# Build manifest and collect conversation files
manifest_sessions="[]"
curl_parts=()

for project_dir in "$CLAUDE_DIR"/*/; do
  [ -d "$project_dir" ] || continue
  project_name=$(basename "$project_dir")
  # Decode working directory from project folder name
  working_dir=$(echo "$project_name" | sed 's|--|/|g; s|^/||')

  for session_file in "$project_dir"*.jsonl; do
    [ -f "$session_file" ] || continue
    session_id=$(basename "$session_file" .jsonl)

    # Skip non-UUID filenames (not session files)
    if ! echo "$session_id" | grep -qE '^[0-9a-f]{8}-'; then
      continue
    fi

    manifest_sessions=$(echo "$manifest_sessions" | jq \
      --arg sid "$session_id" \
      --arg wd "$working_dir" \
      --arg pn "$project_name" \
      '. + [{"session_id": $sid, "working_directory": $wd, "project_name": $pn, "name": $pn}]')

    # Add conversation file to upload
    curl_parts+=(-F "conversation_${session_id}=@${session_file}")
  done
done

count=$(echo "$manifest_sessions" | jq length)
if [ "$count" -eq 0 ]; then
  echo "No sessions found to sync."
  exit 0
fi

echo "Syncing $count sessions with conversations..."

manifest=$(jq -n --argjson sessions "$manifest_sessions" '{"sessions": $sessions}')

response=$(curl -s -X POST "${BASE_URL}/api/sessions/sync" \
  -H "X-API-Key: $API_KEY" \
  -F "manifest=$manifest" \
  "${curl_parts[@]}")

echo "$response" | jq .
echo "Sync complete."

3. Auto-Sync Triggers

Pick one (or combine them) so backups happen automatically.

Option A: Claude Code hook (sync after every session)

Add to ~/.claude/settings.json — triggers when Claude finishes a task:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Stop",
        "command": "claude-session-sync 2>/dev/null &"
      }
    ]
  }
}

Option B: Git post-commit hook (sync on every commit)

Global hook — applies to all repos:

mkdir -p ~/.git-hooks
git config --global core.hooksPath ~/.git-hooks

cat > ~/.git-hooks/post-commit << 'HOOK'
#!/usr/bin/env bash
claude-session-sync 2>/dev/null &
HOOK
chmod +x ~/.git-hooks/post-commit

Option C: Cron job (periodic sync)

# Sync every 15 minutes
(crontab -l 2>/dev/null; echo "*/15 * * * * ~/.local/bin/claude-session-sync >> /tmp/csr-sync.log 2>&1") | crontab -

Option D: Windows Task Scheduler

# PowerShell (admin) — sync every 15 min via WSL/Git Bash
$action = New-ScheduledTaskAction -Execute "bash.exe" `
  -Argument "-lc 'source ~/.bashrc; claude-session-sync'"
$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 15) `
  -Once -At (Get-Date)
Register-ScheduledTask -TaskName "ClaudeSessionSync" `
  -Action $action -Trigger $trigger `
  -Description "Sync Claude Code sessions to registry"

4. Restore on a New Machine

After a crash or on a new machine — download all conversations and restore:

#!/usr/bin/env bash
# claude-session-restore — pull all sessions + conversations from the registry
set -euo pipefail

API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
CLAUDE_DIR="${HOME}/.claude/projects"

echo "Fetching session list..."
sessions=$(curl -s "${BASE_URL}/api/sessions" -H "X-API-Key: $API_KEY")

count=$(echo "$sessions" | jq length)
echo "Found $count sessions to restore."

echo "$sessions" | jq -c '.[]' | while read -r session; do
  sid=$(echo "$session" | jq -r '.session_id')
  project=$(echo "$session" | jq -r '.project_name // .working_directory')
  wd=$(echo "$session" | jq -r '.working_directory')
  conv_size=$(echo "$session" | jq -r '.conversation_size // 0')

  # Encode project dir name the way Claude Code does
  project_dir=$(echo "$project" | sed 's|/|--|g')
  target_dir="${CLAUDE_DIR}/${project_dir}"
  mkdir -p "$target_dir"

  if [ "$conv_size" -gt 0 ]; then
    echo "Downloading conversation for ${sid} (${project_dir})..."
    curl -s "${BASE_URL}/api/sessions/conversation/${sid}" \
      -H "X-API-Key: $API_KEY" \
      -o "${target_dir}/${sid}.jsonl"
  fi

  echo "  → To resume: cd \"${wd}\" && claude --resume ${sid}"
done

echo ""
echo "Restore complete! Sessions are in ${CLAUDE_DIR}/"
echo "Use 'claude --resume SESSION_ID' in the appropriate directory to continue."

5. Obsidian Integration

Export your Claude sessions as Obsidian-compatible markdown notes with YAML frontmatter. Works with Dataview, search, graph view, and tags.

One-time export to vault

#!/usr/bin/env bash
# claude-obsidian-sync — export sessions to Obsidian vault as markdown
set -euo pipefail

API_KEY="${CSR_API_KEY:?Set CSR_API_KEY}"
BASE_URL="${CSR_URL:-https://repo.ittlh.dev}"
VAULT="${OBSIDIAN_VAULT:-$HOME/Documents/obsidian}"
TARGET_DIR="${VAULT}/Claude Sessions"

mkdir -p "$TARGET_DIR"

echo "Fetching sessions for Obsidian export..."
sessions=$(curl -s "${BASE_URL}/api/sessions/export/obsidian" \
  -H "X-API-Key: $API_KEY")

count=$(echo "$sessions" | jq length)
echo "Exporting $count sessions to ${TARGET_DIR}..."

echo "$sessions" | jq -c '.[]' | while read -r entry; do
  filename=$(echo "$entry" | jq -r '.filename' | sed 's/[<>:"|?*]/-/g')
  session_id=$(echo "$entry" | jq -r '.session_id')
  content=$(echo "$entry" | jq -r '.content')

  echo "$content" > "${TARGET_DIR}/${filename}"
  echo "  ✓ ${filename}"
done

echo ""
echo "Done! Open your Obsidian vault to see the notes."
echo "Each note has frontmatter tags for Dataview queries."

Set your vault path

export OBSIDIAN_VAULT="$HOME/Documents/obsidian"

Auto-sync to Obsidian (add to cron or hook)

# Add after claude-session-sync in your hook or cron:
claude-obsidian-sync 2>/dev/null &

Dataview queries for Obsidian

Once synced, use these Dataview queries in any note:

```dataview
TABLE working_directory, updated, conversation_lines
FROM #claude-session
SORT updated DESC
```

```dataview
LIST
FROM #claude-session
WHERE contains(project, "SFLV2")
SORT updated DESC
```

```dataview
TABLE conversation_size as "Size", conversation_lines as "Messages"
FROM #claude-session
WHERE conversation_size > 0
SORT conversation_size DESC
```

What each note looks like

Each session becomes a markdown file with:

What Gets Backed Up

Session metadata Session ID, working directory, project name
Full conversation Complete JSONL transcript — every message, tool call, and result
Code context File reads, edits, and diffs are embedded in the conversation
Resumability Restored sessions can be resumed with claude --resume

API Reference

All endpoints require X-API-Key header.

POST   /api/auth/register                     { "username": "..." } → { api_key }
GET    /api/auth/me                             whoami

GET    /api/sessions                            list sessions (?project= filter)
POST   /api/sessions                            register a session
POST   /api/sessions/sync                       bulk sync (JSON or multipart w/ conversations)
PUT    /api/sessions/:id                        update session
DELETE /api/sessions/:id                        delete session

PUT    /api/sessions/conversation/:sessionId    upload conversation JSONL (raw body)
GET    /api/sessions/conversation/:sessionId    download conversation JSONL

GET    /api/sessions/export/obsidian             all sessions as markdown (JSON array)
GET    /api/sessions/export/obsidian?session_id= single session as .md file