codeburn/docs/sync
iamtoruk c30ebecff4 Merge origin/main into feat/copilot-session-store
Resolves the conflicts #946 accumulated while it was in validation. Eight
files conflicted; the session-store accounting is unchanged.

src/daily-cache.ts — version collision. This PR minted 25 when main was at
24; #1056 (`codex-auto-review` pricing) then spent 25 on main. The bump moves
to 26/MIN 26 and daily-cache.v26.json, with main's full comment ladder kept as
the foundation and this PR's paragraph rewritten to name 26 and record the
collision. PENDING_REDERIVE_PROVIDERS and the B1 migration semantics from
b6481c19 carry over intact, retargeted at 26.

src/models.ts, src/parser.ts, src/audit-report.ts, src/models-report.ts —
#1075/#1078 replaced the per-site "reasoning is already inside output" tests
with billableOutputTokens() and REASONING_INCLUDED_IN_OUTPUT. This PR had
added copilot to that case at three sites independently. Union: all three
sites take main's helper call verbatim, and copilot joins claude and codex in
the set — same accounting this PR shipped, now through main's single source of
truth. It also reaches parser.ts activeGeneratedTokens (a fourth site, from
#1079), which is the same correction: a copilot supplementary call carries
reasoning with output 0, so counting it as generated repeats the per-turn
output. The audit legend already said so on this side.

src/providers/copilot.ts — comment-only. #1054's lastEventTimestamp-first
shutdown fallback was derived from this branch, so the code was already
identical on both sides: the `shutdownTimestamp` expression and the
`copilot:<sid>:shutdown:<model>:<n>` key are byte-for-byte main's. Both
rationales are kept (leg-collapse on date, and residual anchoring).

src/session-cache.ts — PROVIDER_PARSE_VERSIONS.copilot takes this PR's
`-session-store-v3` suffix; main's #1051 note about why a fingerprint change
is expensive is kept above it. Codex keeps main's #1092 suffix chain untouched.

src/main.ts — #1067 deleted the unreachable live dailyMap fallback that this
PR had taught behavioral weight. Main's deletion wins; the now-unused
isBehavioralTurn import goes with it.

tests/parser.test.ts — import union.

Also: scripts/upgrade-path/run.mjs NEW_DAILY_CACHE -> daily-cache.v26.json,
CODEBURN_COPILOT_SESSION_STORE_DB added to the #1064 env-isolation CLEARED
list, and the CHANGELOG entry's stale "v21" corrected to v26.

Verified: tsc clean; 3132 tests pass across 223 files; test:locks 26/26;
verify:upgrade PASSED, re-deriving daily-cache.v26.json and holding durable
copilot history across the bump. #1054's regression ("keeps three stampless
shutdown legs as :n keys with lastEventTimestamp") passes on the merged tree.
Real-corpus A/B against origin/main over 2026-07-01..2026-08-22: codex, grok,
kimicode and opencode byte-identical in export, audit and models; claude drifts
only monotonically with run order (a live session writing transcripts, confirmed
by interleaving four runs). This machine has no copilot data, so the copilot
recovery semantics rest on the suites and the upgrade-path corpus.
2026-08-22 09:36:51 -07:00
..
DEVELOPER.md docs(sync): user guide, developer/protocol reference, README section 2026-07-12 16:05:34 +00:00
README.md Merge origin/main into feat/copilot-session-store 2026-08-22 09:36:51 -07:00

codeburn sync

Push your AI usage telemetry to a shared backend so teams can track adoption, budgets, and ROI across developers.

Everything stays local-first: codeburn never sends data without your explicit action, and prompts/code are never included.

Quick Start

# One-time setup (opens browser for login)
codeburn sync setup https://metrics.your-team.com

# Push recent usage
codeburn sync push

# Check status
codeburn sync status

Commands

codeburn sync setup <url>

Configures sync with a remote endpoint. Opens your browser for a one-time OIDC login.

codeburn sync setup https://metrics.your-team.com

What happens:

  1. Fetches server configuration from <url>/.well-known/codeburn-export.json
  2. Opens your browser to the identity provider's login page
  3. After login, stores a refresh token securely in your OS keychain
  4. Saves the endpoint configuration (no secrets) to ~/.config/codeburn/sync.json

You only need to do this once. The token refreshes silently on every push.

codeburn sync push

Sends unsent AI usage data to the configured endpoint.

# Push unsent calls from the last 7 days (default)
codeburn sync push

# Push a larger window
codeburn sync push --since 30d

# Preview what would be sent
codeburn sync push --dry-run

# Also push git attribution (opt-in — see "Git attribution" below)
codeburn sync push --attribution

codeburn sync status

Shows the current sync configuration and authentication state.

Endpoint: https://metrics.your-team.com
Traces path: /v1/traces
Issuer: https://auth.your-team.com
Auth: configured
Token storage: keychain
Last sync: 2h ago

codeburn sync logout

Removes stored credentials and revokes the token at the identity provider.

codeburn sync logout

codeburn sync reset --confirm

Clears the sent-ledger, causing the next push to re-send all data in the window. Use after a backend migration or if you suspect missing data.

codeburn sync reset --confirm

What Gets Sent

Each AI interaction becomes one OTLP span with these attributes:

Field Example Description
ai.provider kiro, cursor, claude Which AI tool
ai.model claude-sonnet-4-6 Model used
ai.input_tokens 12500 Prompt tokens
ai.output_tokens 3200 Response tokens
ai.cost_usd 0.085 Estimated cost
ai.project my-app Project name
ai.tools ["Edit", "Bash"] Tools invoked

A pseudonymous device_id distinguishes your machines without revealing hostnames.

Git attribution (opt-in: --attribution)

codeburn sync push --attribution additionally sends the session→commit correlation that codeburn yield computes locally, so the backend can join AI usage to git activity without git hooks. Two extra span types are emitted:

codeburn.session.attribution — one per session with joinable evidence:

Field Example Description
ai.project my-app Project name
git.repo github.com/acme/widget Normalized origin remote (credentials and ports stripped)
git.pr_links ["…/pull/12"] PR URLs captured for the session
git.commit_count 2 Number of attributed commits

codeburn.commit — one per commit attributed to a session:

Field Example Description
git.sha 4f2a… Commit SHA
git.in_main true Whether the commit landed in the main branch
git.was_reverted false Whether a later commit reverted it

Attribution is inferred (timestamp-window correlation, the same heuristic as codeburn yield); the resource attribute codeburn.attribution_methodology: timestamp-window marks it as such. State transitions (a commit merging to main, or being reverted) are re-sent automatically on later pushes — receivers should upsert commits by (git.repo, git.sha) and session spans by traceId (the same id usage spans already carry; latest state wins). When a commit migrates to a later-parsed session with a tighter window, the losing session re-emits with git.commit_count: 0 (a retraction), so summing git.commit_count across upserted session rows never double-counts. Retractions fire only when the commit was won by another session — commits that merely age out of the --since window are not retracted, so a previously-synced count stays correct. Session spans also re-emit when an ongoing session's window grows, keeping the span end time current.

With --attribution, normalized repo remote URLs, commit SHAs, commit timestamps (span start times), PR URLs, and the merged/reverted booleans leave your machine — plus the same pseudonymous codeburn.device_id resource attribute the usage spans carry. PR links are rebuilt client-side from scheme + host + path only (userinfo, query strings, and fragments are dropped; https, /org/repo/pull/N path, bounded length, max 20 per session), and the repo identity itself passes a strict hostname/path allow-list before sending — malformed or transport-helper remotes (ext::…, codecommit::…) are rejected outright rather than parsed. Precisely what is and is not sent:

  • Commits: only from repos with a network origin remote, and only for sessions whose own project path resolved to that repo. Local-only repos, file:// remotes, and Windows filesystem paths are never emitted as repo identities. A session whose project path no longer resolves never inherits the repo of the directory you happen to push from.
  • PR links: sent whenever a session captured them, even when the session's repo could not be identified — the PR URL itself names the repo, so this adds no information beyond the link the session already recorded.
  • Without the flag, none of this is sent.

What is NOT sent

  • Prompts — your actual messages to AI are never included
  • Code — file contents, diffs, and paths stay local
  • Bash commands — may contain secrets, never sent
  • Your name/email — identity is derived server-side from your login token

There is no flag to override this. Privacy is structural, not configurable. The only additive opt-in is --attribution (repo remotes, commit SHAs, and PR URLs — never code or prompts), described above.

Authentication

Sync uses standard OIDC (the same protocol as "Sign in with Google"). Your team's admin sets up the identity provider — you just click through the browser login once.

  • Token storage: macOS Keychain, Windows Credential Manager, or Linux libsecret. Falls back to a 0600 file if no keychain is available.
  • Token lifetime: typically 3090 days (set by your admin). You'll be prompted to re-login when it expires.
  • Re-login: run codeburn sync setup <url> again.

FAQ

Q: Does sync run automatically? A: No. You run codeburn sync push when you want. A future version may offer opportunistic push (after each codeburn report), but it's always explicit.

Q: What if I push the same data twice? A: Safe. A local sent-ledger tracks what's been sent. Re-pushing the same window doesn't create duplicates.

Q: Why is today's Copilot usage missing from my dashboard? A: Copilot input/cache usage is reconciled locally between the per-request session-store.db rows and the session.shutdown rollups, and that reconciliation keeps changing while a session is live — a rollup residual shrinks as the rows covering it land, and a row that looked like a crash-only request becomes supplementary once its journal entry appears. The sent-ledger is append-once, so a value sent mid-reconciliation could never be corrected at the receiver. A Copilot session is therefore held back until it has been quiet for 24 hours, then pushed once, final. Nothing is dropped; --dry-run reports how many calls are held.

Q: Why didn't my old Copilot sessions resync with the new per-request breakdown? A: On purpose. A Copilot session's input/cache can leave your machine in one of two shapes — as one session.shutdown rollup span per (session, model), or reconciled into one span per API request plus a residual for whatever the rows don't cover. They describe the same tokens, so the receiver must never hold both, and a usage span cannot be retracted once the append-once ledger has sent it. Whichever shape a session was first synced in, it stays in; the other is frozen for that session permanently. That runs in both directions: a session synced by a pre-store version keeps its rollup and never sends rows, and a session synced as rows never sends a rollup later — which matters because at the 90-day durable age-out the cached rows are pruned and the rollup starts serving again under a key that was never sent. Growth within the shape a session already uses is unaffected, and per-turn output spans are never frozen. --dry-run reports the frozen count. codeburn sync reset --confirm clears the local ledger and re-pushes everything under the new breakdown — only do that if the receiver's copy is cleared too, or you get exactly the doubling this avoids.

Q: What if I'm offline for a week? A: Next push catches up. The default window is 7 days; use --since 30d or --since all (up to 6 months) for longer gaps. A push runs to completion regardless of size — server rate limits (429) are waited out automatically.

Q: Can my admin see my prompts? A: No. Prompts are never included in the payload. The server only sees token counts, costs, model names, and project names.

Q: How do I stop syncing? A: codeburn sync logout removes everything. Or just stop running push.