Implement the following plan:
Add git metadata to traces
Context
Traces store a directory path but no git context. When a trace gets shared or linked to a PR, we can't tell which repo, branch, or ref it came from without re-deriving it from the directory. Adding gitRemoteUrl, gitBranch, and gitRef to the trace schema lets us store this at discovery time and send it up with the trace to the server, where it becomes queryable.
This touches three layers: CLI local storage (SQLite), the API sync path, and the Convex backend schema.
Branch
New branch from main (or from feat/traces-active-command if that has merged).
Changes
1. CLI types — cli/src/types.ts
Add three optional fields to TraceMetadata:
typescript
2. CLI SQLite migration — cli/src/core/migrations.ts
Bump CURRENT_SCHEMA_VERSION to 4. Add migration:
typescript
3. CLI EventStore — cli/src/core/event-store.ts
- Add
git_remote_url TEXT, git_branch TEXT, git_ref TEXT to the CREATE TABLE statement
upsertTrace(): add the three columns to INSERT and ON CONFLICT (use COALESCE to preserve existing values)
updateTrace(): add the three fields to the dynamic update builder
getTrace(): add the three columns to the SELECT
listTraces(): add the three columns to the SELECT
- Map between camelCase (
gitRemoteUrl) in TypeScript and snake_case (git_remote_url) in SQL, matching the existing shared_url/sharedUrl pattern
4. Git resolution utility — new file cli/src/core/git.ts
A small utility that resolves git metadata from a directory path:
typescript
Runs git -C <dir> config --get remote.origin.url, git -C <dir> rev-parse --abbrev-ref HEAD, and git -C <dir> rev-parse HEAD via Bun.spawnSync (or child_process.execSync). Returns empty object if directory isn't a git repo or commands fail. Each command runs independently so partial results are fine (e.g. a repo with no remote still returns branch and ref).
5. Adapter integration — cli/src/adapters/claude-code/v1.ts
In getTraces() and getTraceIndexBatch(), after setting directory on each trace, call resolveGitInfo(directory) and set the three fields. This happens at scan time so the data is captured when the trace is fresh.
Performance: resolveGitInfo runs three quick local git commands. The adapters already do file I/O per trace, so this adds minimal overhead. We can cache by directory (most traces in a batch share the same directory) to avoid redundant calls.
6. CLI API sync — cli/src/services/api.ts
Extend SyncTracePayload with the three new fields:
typescript
Update buildTracePayload() to include them from TraceMetadata. Update syncTraceMetadata() to send them in the request body.
7. OpenAPI spec — packages/shared/src/api/v1/openapi.json
Add gitRemoteUrl, gitBranch, gitRef as optional string properties to:
TraceCreateRequest
TraceUpsertRequest
TraceDetail (in the allOf extension object, alongside projectPath)
Then regenerate types: cd packages/shared && bun run generate:api
8. API validators — api/convex/lib/validators.ts
Add to both TraceCreateSchema and TraceUpsertSchema:
typescript
9. Convex schema — api/convex/schema.ts
Add to the traces table definition:
typescript
Add an index for querying by repo: .index("by_gitRemoteUrl", ["gitRemoteUrl"])
10. Internal sync mutation — api/convex/internal/sync.ts
Add the three fields to traceInput args. In upsertTrace handler, include them in both the insert and the conditional update block (same pattern as projectPath).
11. Service layer and response mapping — api/convex/services/traces.ts
mapTraceDetail(): include gitRemoteUrl, gitBranch, gitRef in the returned object
createTrace(), upsertTrace(), updateTrace(): pass the three fields through to the internal mutation
12. Tests
cli/src/core/git.test.ts: test resolveGitInfo with a real temp git repo (init, add remote, commit, check all three fields resolve). Test with non-git directory returns empty. Test with repo that has no remote returns branch+ref but no remoteUrl.
cli/src/core/migrations.test.ts: test that migration 4 adds the three columns and is idempotent.
cli/src/core/event-store.test.ts: test that upsert/get round-trips the new fields, and that COALESCE preserves existing values on re-upsert.
Files to modify
cli/src/types.ts # TraceMetadata type
cli/src/core/migrations.ts # v4 migration
cli/src/core/event-store.ts # CRUD for new columns
cli/src/core/git.ts # NEW — git resolution utility
cli/src/core/git.test.ts # NEW — tests
cli/src/adapters/claude-code/v1.ts # resolve git info at scan time
cli/src/services/api.ts # sync payload
packages/shared/src/api/v1/openapi.json # API spec
packages/shared/src/api/v1/gen/types.gen.ts # regenerated
api/convex/schema.ts # Convex traces table
api/convex/lib/validators.ts # request validation
api/convex/internal/sync.ts # internal mutation args + handler
api/convex/services/traces.ts # service layer + response mapping
Verification
cd cli && bun test — all existing + new tests pass
- Open TUI, confirm traces load normally (migration runs silently on startup)
- Check a trace in SQLite:
sqlite3 ~/.traces/traces.db "SELECT id, git_remote_url, git_branch, git_ref FROM traces LIMIT 5" — should show git data for traces in git repos
- Share a trace:
traces share --trace-id <id> --json — confirm the API accepts the new fields without error
cd api && bun test — Convex tests pass
- Deploy API, verify
GET /v1/traces/:externalId returns gitRemoteUrl, gitBranch, gitRef in the response
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /Users/andrew/.claude/projects/-Users-andrew-code-traces-traces/a8f01d3c-295f-4043-ae2a-417299a74100.jsonl