# Orchestration Runbook: User Identity + Repo Tracking

Companion to `docs/run-07022026/user-identity-repo-track.md`. Step-by-step playbook for launching 3 parallel Claude Code agents (2 parallel tracks + 1 sequential integration track) using git worktrees.

---

## Prerequisites

```bash
# Verify Claude Code is installed
claude --version

# Verify git worktree support
git worktree list

# Ensure we're on main and up to date
cd /home/sagar/trace
git checkout main
git pull

# Create shared directories
mkdir -p /home/sagar/trace/worktrees
mkdir -p /home/sagar/trace/project_logs

# AFTER creating worktrees (Step 1), copy auth to each:
# cp .quickcall.env worktrees/track-a/
# cp .quickcall.env worktrees/track-b/
# cp .quickcall.env worktrees/track-c/
```

### GitHub Issue Reporting: MCP vs `gh` CLI

**`gh` CLI is NOT authenticated on this machine.** Agents MUST use QuickCall MCP tools for GitHub operations. Do NOT put `gh` CLI examples in CLAUDE.md — agents will try them, fail, and waste time hacking around with raw `curl`/`urllib`.

The correct tool for issue comments is the `manage_issues` MCP tool:
```
Tool: manage_issues
Parameters:
  action: "comment"
  owner: "quickcall-dev"
  repo: "trace"
  issue_numbers: [ISSUE_NUMBER]
  body: "comment text"
```

For checking off checkboxes, use:
```
Tool: manage_issues
Parameters:
  action: "update"
  owner: "quickcall-dev"
  repo: "trace"
  issue_numbers: [ISSUE_NUMBER]
  body: "updated body with [x] checkboxes"
```

### Runtime Environment

- Use `uv run` for all Python commands (e.g., `uv run pytest tests/`)
- Virtual environment is `.venv` in each worktree directory
- Copy `.quickcall.env` from main repo into each worktree for MCP GitHub access

---

## Step 1: Create Git Worktrees + Branches

Run from the main repo (`/home/sagar/trace`):

```bash
# Create the parent feature branch
git branch feat/session-context

# Create track branches off it
git branch feat/session-context-schema   # Track A
git branch feat/session-context-resolver # Track B
git branch feat/session-context-wiring   # Track C

# Create worktrees
git worktree add worktrees/track-a feat/session-context-schema
git worktree add worktrees/track-b feat/session-context-resolver
git worktree add worktrees/track-c feat/session-context-wiring

# Verify
git worktree list
```

---

## Step 2: Create CLAUDE.md in Each Worktree

### worktrees/track-a/CLAUDE.md

```markdown
# Agent: track-a — Schema & Data Pipeline

You are adding SessionContext to the unified schema and wiring it through the DB and server layers.

## Your Task
Read the full plan: /home/sagar/trace/docs/run-07022026/user-identity-repo-track.md (Steps 1 + 3)

## Deliverables

### Step 1: Add SessionContext to unified schema
- `qc_trace/schemas/unified.py` — Add `SessionContext` dataclass with fields: user_email, user_name, device_name, cwd, repo_url, repo_name, git_branch, git_commit, project_hash. Add `session_context: SessionContext | None = None` field to `NormalizedMessage`.
- `qc_trace/server/handlers.py` — Add `_parse_session_context(data: dict | None) -> SessionContext | None` helper. Update `_dict_to_normalized_message` to call it.

### Step 3: Update DB schema
- `qc_trace/db/schema.sql` — Add 9 columns to sessions table (user_email, user_name, device_name, cwd, repo_url, repo_name, git_branch, git_commit, project_hash). Add indexes on user_email and repo_name. Bump schema version to 2.
- `qc_trace/db/migrations.py` — Bump `CURRENT_SCHEMA_VERSION` to 2.
- `qc_trace/db/writer.py` — Update `_upsert_sessions` to write all new columns from `msg.session_context`. Use COALESCE in ON CONFLICT for incremental enrichment.
- `qc_trace/server/handlers.py` — Update `handle_sessions` to accept new session context fields in POST body.

## Constraints
- Python ≥ 3.11 (use X | Y union syntax)
- No new dependencies — SessionContext is a stdlib dataclass
- Use `uv run` for all commands (e.g., `uv run pytest tests/`, `uv run ruff check`)
- COMMIT after each file change. Do NOT leave changes uncommitted.
- Use conventional commits: feat:, fix:, test:, etc.
- No Claude/Anthropic attribution in commits.
- Push your branch when all work is done: `git push -u origin feat/session-context-schema`

## MANDATORY: GitHub Issue Reporting

You MUST update GitHub issue #ISSUE_NUMBER at every milestone using the manage_issues MCP tool.
Do NOT use `gh` CLI — it is not authenticated. Use MCP tools only.

### When you START work:
Use manage_issues with action="comment", issue_numbers=[ISSUE], body="## Status: In Progress\n\nStarting work on..."

### After EACH commit:
Use manage_issues with action="comment", body="## Progress Update\n\n**Completed:** file — description\n**Commit:** SHA\n**Next:** next file"

### When ALL work is done:
Use manage_issues with action="comment", body="## Status: Completed\n\nAll files committed, tests passing.\n**Commits:** list them"

### Check off completed items:
Use manage_issues with action="update" to update the issue body with [x] checkboxes.

## Coordination
- You have NO blockers — start immediately
- Write progress to: /home/sagar/trace/project_logs/track-a_progress.json
- Track C depends on you. Update progress when done.

## Progress JSON Format
Write this after each major milestone:
{
  "status": "in_progress|completed",
  "current_task": "description of what you're doing",
  "completed_tasks": ["task1", "task2"],
  "errors": [],
  "provides_context": {
    "session_context_location": "qc_trace/schemas/unified.py",
    "writer_location": "qc_trace/db/writer.py"
  }
}
```

### worktrees/track-b/CLAUDE.md

```markdown
# Agent: track-b — Repo Resolver Utility

You are creating a standalone utility module for git repository resolution, identity detection, and Cursor/Gemini path parsing.

## Your Task
Read the full plan: /home/sagar/trace/docs/run-07022026/user-identity-repo-track.md (Step 2)

## Deliverables

### New file: qc_trace/utils/repo_resolver.py
- `RepoInfo` dataclass (cwd, repo_url, repo_name, git_branch, git_commit, user_email, user_name)
- `resolve_repo(cwd: str) -> RepoInfo` — run git commands against cwd to get repo + identity info
- `resolve_global_identity() -> tuple[str | None, str | None]` — git config --global user.email/user.name
- `decode_cursor_slug(slug: str) -> str | None` — decode '-home-sagar-trace' → '/home/sagar/trace', validate path exists on disk
- `extract_cursor_slug_from_path(file_path: str) -> str | None` — extract slug from ~/.cursor/projects/{slug}/...
- `extract_gemini_hash_from_path(file_path: str) -> str | None` — extract hash from ~/.gemini/tmp/{hash}/chats/...
- `_git_cmd(cwd: str, cmd: list[str]) -> str | None` — run git command, return stdout or None
- `_extract_repo_name(url: str) -> str` — parse 'org/repo' from SSH or HTTPS git URL

### New file: tests/test_repo_resolver.py
- Test slug decode (happy path, Windows-style, non-existent path)
- Test repo name extraction from SSH URLs (git@github.com:org/repo.git)
- Test repo name extraction from HTTPS URLs (https://github.com/org/repo.git)
- Test extract_cursor_slug_from_path with various path patterns
- Test extract_gemini_hash_from_path with various path patterns

## Constraints
- All stdlib — uses subprocess.run with timeout=5, os.path.isdir, re
- ZERO imports from qc_trace modules — this is fully standalone
- RepoInfo is its own dataclass, NOT SessionContext (that's in unified.py)
- Use `uv run` for all commands (e.g., `uv run pytest tests/`, `uv run ruff check`)
- COMMIT after each file change. Do NOT leave changes uncommitted.
- Use conventional commits: feat:, fix:, test:, etc.
- No Claude/Anthropic attribution in commits.
- Push your branch when all work is done: `git push -u origin feat/session-context-resolver`

## MANDATORY: GitHub Issue Reporting

You MUST update GitHub issue #ISSUE_NUMBER at every milestone using the manage_issues MCP tool.
Do NOT use `gh` CLI — it is not authenticated. Use MCP tools only.

(Same comment format as Track A — see above.)

## Coordination
- You have NO blockers — start immediately
- Write progress to: /home/sagar/trace/project_logs/track-b_progress.json
- Track C depends on you. Update progress when done.

## Progress JSON Format
Write this after each major milestone:
{
  "status": "in_progress|completed",
  "current_task": "description of what you're doing",
  "completed_tasks": ["task1", "task2"],
  "errors": [],
  "provides_context": {
    "repo_resolver_location": "qc_trace/utils/repo_resolver.py",
    "public_api": ["resolve_repo", "resolve_global_identity", "decode_cursor_slug", "extract_cursor_slug_from_path", "extract_gemini_hash_from_path"]
  }
}
```

### worktrees/track-c/CLAUDE.md

```markdown
# Agent: track-c — Collector Integration + Daemon Wiring

You are wiring SessionContext population into all 4 source collectors and connecting identity resolution at daemon startup.

## Your Task
Read the full plan: /home/sagar/trace/docs/run-07022026/user-identity-repo-track.md (Steps 4 + 5)

## Deliverables

### Step 4: Populate SessionContext in collectors
- `qc_trace/daemon/collector.py` — Update `collect_file` signature to accept device_name, global_email, global_name. In each of the 4 source collectors (_collect_claude, _collect_codex, _collect_gemini, _collect_cursor), populate SessionContext on all returned messages.
  - Claude Code: grab cwd + gitBranch from parsed lines → resolve_repo(cwd) → SessionContext
  - Codex CLI: grab cwd from session_meta → resolve_repo(cwd), prefer raw git info from Codex data → SessionContext
  - Gemini CLI: extract projectHash from session JSON or file path → SessionContext with global_email/device_name
  - Cursor: extract slug from file path → decode_cursor_slug → resolve_repo(cwd) → SessionContext
- `qc_trace/schemas/codex_cli/transform.py` — Extend `CodexTransformContext` with cwd and git fields from session_meta.

### Step 5: Wire daemon startup
- `qc_trace/daemon/main.py` — In Daemon.run(), resolve device_name via socket.gethostname() and global git identity via resolve_global_identity() once on startup. Pass device_name, global_email, global_name to collect_file() in _poll_cycle.

## Constraints
- Imports SessionContext from qc_trace.schemas.unified (from Track A)
- Imports resolve_repo, resolve_global_identity, decode_cursor_slug, extract_cursor_slug_from_path, extract_gemini_hash_from_path from qc_trace.utils.repo_resolver (from Track B)
- Identity chain: local git config (from resolve_repo) > global git config (global_email) > device_name
- Use `uv run` for all commands (e.g., `uv run pytest tests/`, `uv run ruff check`)
- COMMIT after each file change. Do NOT leave changes uncommitted.
- Use conventional commits: feat:, fix:, test:, etc.
- No Claude/Anthropic attribution in commits.
- Push your branch when all work is done: `git push -u origin feat/session-context-wiring`

## MANDATORY: GitHub Issue Reporting

You MUST update GitHub issue #ISSUE_NUMBER at every milestone using the manage_issues MCP tool.
Do NOT use `gh` CLI — it is not authenticated. Use MCP tools only.

(Same comment format as Track A — see above.)

## Coordination
- BLOCKED BY: Track A + Track B (you need SessionContext in unified.py AND repo_resolver.py)
- Check blocker status:
  - cat /home/sagar/trace/project_logs/track-a_progress.json
  - cat /home/sagar/trace/project_logs/track-b_progress.json
- When BOTH show "status": "completed", merge their branches and start:
  - git merge feat/session-context-schema
  - git merge feat/session-context-resolver
- Write progress to: /home/sagar/trace/project_logs/track-c_progress.json

## Progress JSON Format
Write this after each major milestone:
{
  "status": "in_progress|completed",
  "current_task": "description of what you're doing",
  "completed_tasks": ["task1", "task2"],
  "errors": [],
  "provides_context": {
    "collector_location": "qc_trace/daemon/collector.py",
    "main_location": "qc_trace/daemon/main.py"
  }
}
```

---

## Step 3: Launch Agents

Open 3 terminal sessions (or tmux panes).

### Phase 1 — Launch Track A and Track B in parallel (no blockers)

```bash
# Terminal 1: Track A — Schema & Data Pipeline
cd /home/sagar/trace/worktrees/track-a
claude --dangerously-skip-permissions

# Terminal 2: Track B — Repo Resolver Utility
cd /home/sagar/trace/worktrees/track-b
claude --dangerously-skip-permissions
```

### Phase 2 — Launch Track C after A + B complete

Monitor progress:
```bash
watch -n 10 'echo "=== Track A ===" && cat /home/sagar/trace/project_logs/track-a_progress.json 2>/dev/null || echo "(not started)" && echo && echo "=== Track B ===" && cat /home/sagar/trace/project_logs/track-b_progress.json 2>/dev/null || echo "(not started)"'
```

Once **both** show `"status": "completed"`:

```bash
# Terminal 3: Track C — Collector Integration + Daemon Wiring
cd /home/sagar/trace/worktrees/track-c

# Pull in both dependencies
git merge feat/session-context-schema
git merge feat/session-context-resolver

claude --dangerously-skip-permissions
```

---

## Step 4: Monitor All Agents

### Dashboard (single terminal)

```bash
for f in /home/sagar/trace/project_logs/track-*_progress.json; do
  echo "=== $(basename $f) ==="
  python3 -c "
import json, sys
d = json.load(open('$f'))
print(f\"  Status: {d.get('status', '?')}\")
print(f\"  Task:   {d.get('current_task', '?')}\")
print(f\"  Done:   {len(d.get('completed_tasks', []))} tasks\")
print(f\"  Errors: {len(d.get('errors', []))}\")
" 2>/dev/null || echo "  (not started)"
  echo
done
```

Save as `scripts/session-context-status.sh` and run:
```bash
watch -n 15 bash /home/sagar/trace/scripts/session-context-status.sh
```

### Git activity across worktrees

```bash
git worktree list | while read dir branch rest; do
  echo "=== $dir ($branch) ==="
  git -C "$dir" log --oneline -3 2>/dev/null || echo "  (no commits)"
  echo
done
```

---

## Step 5: Handle Agent Failures / Restarts

If a Claude Code session dies:

```bash
# 1. Check what the agent accomplished
cd /home/sagar/trace/worktrees/<track-name>
git log --oneline -10

# 2. Check progress file
cat /home/sagar/trace/project_logs/<track-name>_progress.json

# 3. Restart Claude Code in the same worktree
claude --dangerously-skip-permissions
```

The agent picks up where it left off because:
- Git commits preserve all completed work
- The progress JSON shows what's done vs remaining
- The CLAUDE.md contains the full task specification

### If an agent is stuck / looping

```bash
# Kill the session (Ctrl+C)
cd /home/sagar/trace/worktrees/<track-name>
git log --oneline -5
git diff HEAD~1

# Optionally revert a bad commit
git revert HEAD

# Add a hint to CLAUDE.md then restart
echo "\n## Hint\nThe previous approach of X didn't work. Try Y instead." >> CLAUDE.md
claude --dangerously-skip-permissions
```

---

## Step 6: Merge All Tracks

Once all 3 tracks report `"status": "completed"`:

```bash
cd /home/sagar/trace

# Merge in dependency order onto the parent feature branch
git checkout feat/session-context

# 1. Track A — schema + DB pipeline (no deps)
git merge --no-ff feat/session-context-schema -m "feat: add SessionContext schema and DB pipeline"
pytest tests/

# 2. Track B — repo resolver (no deps)
git merge --no-ff feat/session-context-resolver -m "feat: create repo_resolver utility"
pytest tests/

# 3. Track C — collector wiring (depends on A + B)
git merge --no-ff feat/session-context-wiring -m "feat: wire session context into collectors and daemon"
pytest tests/

# 4. If conflicts arise, resolve manually or launch a Claude session:
# claude --dangerously-skip-permissions
# "Resolve the merge conflicts. Run pytest tests/ to verify."

# 5. Full integration test
docker compose down -v && docker compose up -d
python -m qc_trace.cli.traced db init
python -m qc_trace.cli.traced start
sleep 15  # wait for a poll cycle
curl -s localhost:7777/api/sessions | python3 -m json.tool
# Verify sessions have: user_email, cwd, repo_name, git_branch
```

### Merge to main

```bash
git checkout main
git merge --no-ff feat/session-context -m "feat: add user identity and repo tracking to sessions"
pytest tests/
```

### Cleanup worktrees

```bash
git worktree remove worktrees/track-a
git worktree remove worktrees/track-b
git worktree remove worktrees/track-c

# Delete merged branches
git branch -d feat/session-context-schema feat/session-context-resolver feat/session-context-wiring
```

---

## Step 7: Create PR

```bash
git push -u origin feat/session-context

gh pr create \
  --title "feat: add user identity and repo tracking to sessions" \
  --body "$(cat <<'EOF'
## Summary
- Add `SessionContext` dataclass to unified schema (user_email, user_name, device_name, cwd, repo_url, repo_name, git_branch, git_commit, project_hash)
- Create standalone `repo_resolver` utility for git resolution, identity detection, Cursor slug decode, Gemini hash extraction
- Add 9 columns to sessions DB table with COALESCE upsert for incremental enrichment
- Populate SessionContext in all 4 source collectors (Claude Code, Codex CLI, Gemini CLI, Cursor)
- Resolve device_name + global git identity at daemon startup as fallback

## Built by 3 parallel Claude Code agents
- **Track A**: SessionContext schema + DB pipeline (unified.py, schema.sql, writer.py, handlers.py)
- **Track B**: repo_resolver utility (repo_resolver.py + tests)
- **Track C**: Collector integration + daemon wiring (collector.py, main.py, codex transform)

## Test plan
- [ ] `pytest tests/` passes (including new test_repo_resolver.py)
- [ ] DB reset + init creates sessions table with new columns
- [ ] Claude Code sessions have user_email, cwd, repo_name, git_branch
- [ ] Codex sessions have cwd + git info + user_email
- [ ] Gemini sessions have project_hash + user_email (global) + device_name
- [ ] Cursor sessions have decoded cwd + repo info + user_email
- [ ] Sessions without git show device_name as fallback identity
EOF
)"
```

---

## Quick Reference: tmux Layout

```bash
# Create session with 3 side-by-side panes (A | B | C)
tmux new-session -d -s session-ctx -c /home/sagar/trace/worktrees/track-a
tmux split-window -h -t session-ctx -c /home/sagar/trace/worktrees/track-b
tmux split-window -h -t session-ctx -c /home/sagar/trace/worktrees/track-c
tmux select-layout -t session-ctx even-horizontal

# Attach
tmux attach -t session-ctx

# In each pane, run: claude --dangerously-skip-permissions
```

### Pane layout:
```
┌──────────────────┬──────────────────┬──────────────────┐
│     Track A      │     Track B      │     Track C      │
│   Schema + DB    │  Repo Resolver   │     Wiring       │
│   (Phase 1)      │   (Phase 1)      │   (Phase 2)      │
└──────────────────┴──────────────────┴──────────────────┘
```

---

## Timeline

```
Phase 1: Track A + Track B in parallel     (~30-60 min)
Phase 2: Track C (after A+B merge)         (~30-60 min)
Phase 3: Merge + integration testing       (~15 min)
───────────────────────────────────────────
Total:                                     ~1-2 hours active
```

Tracks A and B can run unattended. Check in when both complete to launch Track C.

---

## Actual Run Results

### Merge

All 3 track branches merged cleanly into `feat/session-context` — zero conflicts.

```bash
git checkout feat/session-context
git merge --no-ff origin/feat/session-context-schema -m "feat: merge Track A (#46)"    # clean
git merge --no-ff origin/feat/session-context-resolver -m "feat: merge Track B (#47)"  # clean
git merge --no-ff origin/feat/session-context-wiring -m "feat: merge Track C (#48)"    # clean
```

### Test results

- **220 passed**, 1 pre-existing failure (`test_status_running_no_server` — unrelated)
- DB tests required schema reset: `docker compose down -v && docker compose up -d`

### Cleanup

```bash
# Kill tmux session
tmux kill-session -t session-ctx

# Remove worktrees (--force needed for Track A's uncommitted changes)
git worktree remove --force worktrees/track-a
git worktree remove --force worktrees/track-b
git worktree remove --force worktrees/track-c

# Delete local branches
git branch -d feat/session-context-schema feat/session-context-resolver feat/session-context-wiring

# Delete remote branches
git push origin --delete feat/session-context-schema feat/session-context-resolver feat/session-context-wiring

# Push merged branch + update PR
git push origin feat/session-context
```

### PR

**#49** — `feat/session-context` → `main` — marked ready for review
https://github.com/quickcall-dev/trace/pull/49

11 files changed, +658 -30

---

## Lessons Learned (from actual run)

### 1. Agents don't commit unless explicitly told

Track A completed all code changes but left them uncommitted. Track C then had to reimplement Track A's work from scratch. **Fix:** CLAUDE.md must say "COMMIT after each file change. Do NOT leave changes uncommitted." (updated above).

### 2. `gh` CLI was not authenticated — agents wasted time hacking around it

All 3 agents tried `gh issue comment` first, failed, then improvised:
- Track A: `curl` + raw GitHub API with token from `.quickcall.env`
- Track B: Python `urllib.request` + raw GitHub API (posted duplicate comments)
- Track C: gave up entirely, posted zero comments

**Fix:** CLAUDE.md must explicitly say "Do NOT use `gh` CLI" and provide the exact MCP tool name (`manage_issues`) with parameters. Never use `gh` examples.

### 3. MCP tools may not be available in worktree-launched sessions

Agents launched via `claude --dangerously-skip-permissions` in worktree directories may not inherit MCP server configuration. **Fix:** Verify MCP availability before launch, or copy `.quickcall.env` to each worktree and use the raw API as a documented fallback.

### 4. Copy `.quickcall.env` preemptively

Don't wait for agents to discover auth problems. Copy the env file during worktree setup:
```bash
cp /home/sagar/trace/.quickcall.env /home/sagar/trace/worktrees/track-a/
cp /home/sagar/trace/.quickcall.env /home/sagar/trace/worktrees/track-b/
cp /home/sagar/trace/.quickcall.env /home/sagar/trace/worktrees/track-c/
```

### 5. Use `uv run` not bare `pytest`/`ruff`

The virtualenv is managed by `uv`. Agents that ran bare `pytest` hit import errors. **Fix:** All CLAUDE.md examples must use `uv run pytest`, `uv run ruff check`, etc.

### 6. Test one agent's reporting flow before launching all

Do a dry run: launch one agent, have it post a single issue comment, verify it works. Then launch the rest.

### 7. Always validate daemon changes visually via the dashboard

Code changes to the daemon/collector path MUST be validated end-to-end:
1. Kill old daemon + server processes (`kill PID`)
2. Reset DB: `docker compose down -v && docker compose up -d`
3. Start server with new code: `uv run python -m qc_trace.server.app`
4. Start daemon with new code: `uv run python -m qc_trace.daemon`
5. **Wait for daemon reconciliation** — on startup, it compares local state against the DB. After a DB reset, it resets all file states and re-processes everything.
6. Check the dashboard — sessions should appear with the new fields populated.

**Critical:** the daemon must be **restarted** after a DB reset. Reconciliation only runs at startup. If the daemon is already running when the DB is wiped, it won't re-push old sessions because its local state says "already processed."
