Repository navigation
fix(telemetry): one call, one key; one repository, one id - #1049
Draft
blafourcade wants to merge 14 commits into
Draft
blafourcade wants to merge 14 commits into
blafourcade wants to merge 14 commits into
Conversation
The host is case-folded on every form: WHATWG URL folds only special schemes, so ssh:// and git:// kept it. ssh.github.com is github.com (GitHub's SSH over port 443). Owner and repo are case-folded on github.com only, which documents them as not case sensitive; any other host keeps their case. Found by the R1 telemetry audit (M7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A call with no requestId was keyed message.id:sessionId:timestamp, so copies of it written at different times were never folded and the call was counted once per copy. The Messages API makes message.id a unique object identifier, so message.id:sessionId names one call. Found by the R1 telemetry audit (M10). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Data stored by released CLIs carries the earlier ids and keys. Every reader of the ledger, roots.json and branches.jsonl now re-keys a timestamped key and re-ids through ledger/repository-ids.json, a table from the id an earlier version gave a repository to its id now, made once from each root's origin. The first ingest or report writes the table, rewrites roots.json and every ledger partition, then marks it complete, under the ledger lock, by write-then-rename: a run stopped at any write is finished by the next. branches.jsonl stays append-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The usage contract states the requestless key, the repository address rule and the new ledger/repository-ids.json store; the CLI memory points at the migration. The task folder holds the contract with the store inventory, the plan, the red runs and the hand mutations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
…one-repository-one-id
It settled only the partitions a read finds unsettled, and a read never marks a day for its line order, so a day the new rules left alone kept the order it was found in. It now saves every partition from one full load; a day already canonical is not rewritten. Pins the counterexample the fresh-ingest property found. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
…pread Its key no longer carries the timestamp, and no real transcript bounds how far apart its copies are written (none of 512,820 usage lines had no requestId). An ingest storing one now reads every day, so the ledger holds it once and a bounded report agrees with the full one. Calls with a requestId keep the measured one-day window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A root unreadable or gone at the first run stayed unmapped while the table was marked complete, so its repository got a second id. Such roots are now kept pending and looked at again by every ingest; when one adds an id, the stores are rewritten under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The migration and the snapshot store's alias table are required, so a wiring that leaves one out fails to compile. Re-keying an earlier key applies to claude-code records only. Fixes a stale class name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The usage contract and the task contract state that a requestless call is stored once, that a root unread at migration stays pending until it can be read, and what a damaged table costs branches.jsonl. Evidence holds the measurement, the red runs, the first-run cost and the round one review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The re-keying and the requestless test shared a copy of the advisor suffix pattern; they now use the format's own, pinned with a two-digit iteration. The migration's early return for a complete table with nothing pending changed nothing: that run locates nothing and writes nothing, so the guard is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
…erties The migration property's first run now has clones unreadable or gone, back by the next. A failure at every write of a pending root's retry ends as a clean retry. The window property generates requestless lines an older CLI writes after the migration and exempts the bounded read for those calls alone, the stated limit. The ledger and roots adapters say why they build their alias table themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 What & why
One repository could get several ids, and one call several keys, so the report split repositories and counted some calls more than once. Two holes from the R1 telemetry audit:
ssh://orgit://host kept its case too (WHATWGURLfolds only special schemes).git@github.com:Example/X.gitandhttps://github.com/example/xgave two ids.requestIdwas keyedmessage.id:sessionId:timestamp. Its copies carry different timestamps, so they were never folded.Data already stored by released CLIs is migrated (maintainer decision: the measurement is a beta).
🛠️ How it works
ssh.github.comis read asgithub.com(GitHub: SSH over the HTTPS port).github.comonly, which its REST docs say are "not case sensitive".requestIdis keyedmessage.id:sessionId. The Messages API documentsidas a "Unique object identifier". Keys that carry arequestIdare unchanged.origincan say which id an earlier version gave it.roots.jsononce. It writesledger/repository-ids.json, a table from each earlier id to the id it has now.roots.jsonand every ledger partition, re-keyed, re-ided and folded, and marks the tablecompletelast.roots.jsonandbranches.jsonlapplies the key rule and the table.branches.jsonlis never rewritten, so it stays append-only.requestId, and copies of the 184,502 message ids that do have one were at most 0.27 h apart.requestId.requestId.pendingin the table and looked at again by every ingest. When it adds an id,roots.jsonand the ledger are rewritten again, so the repository ends with one id.57169330). The migration rewrites every day file, not only those a read finds unsettled, so each day is in the order one ingest writes it.🧪 How to verify
cd cli && pnpm exec vitest run tests/contexts/telemetry/application/legacyruns three fast-check properties:cd cli && pnpm test: 6,809 passed, 1 skipped.node --test 'scripts/__tests__/**/*.test.js': 461 of 461 passed.pnpm smoke: PASS 132, FAIL 0.--attended: both passed.pnpm test:mutation:telemetry: 97.2, floor 96 (full scope, before the review fixes).57169330: 98.8, one equivalent survivor (the$anchor of the advisor suffix;#advisoronly ever appears as a key's suffix).gitonce per root inroots.json. Measured: 4.5 s for 50 roots and 250,000 records, 20.8 s for 200 roots and 1,000,000 records, against the hook's 110 s. It addsledger/repository-ids.json.ledger/repository-ids.jsononce, on machines that never ranontoo, with an empty table there.kill -9, are reasoned through, not exercised: the table stays incomplete until the rewrite ends, so the next run redoes it all.repository-ids.jsonis rebuilt from the clones still there.branches.jsonlis never rewritten, so it loses the alias of a repository with no clone left.undatedhashes inleft-out.txtcannot be re-keyed. That file is unreleased: cli-v5.5.0 never wrote it.github.comkeep owner and repo case.🔗 Linked issue
None. Found by the R1 telemetry audit (M7, M10).
✅ I certify
🤖 Generated with Claude Code
https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp