Repository navigation
feat(telemetry): deterministic claude code token measurement, release 1 - #993
Draft
blafourcade wants to merge 75 commits into
Draft
blafourcade wants to merge 75 commits into
blafourcade wants to merge 75 commits into
Conversation
The contract for replacing the previous telemetry: every billed Claude Code call counted once, broken down by person, session, model, period, repository, task and ticket, the task coming only from a declaration. Carries the measurements, the common usage record and the prototype readers with their mutation-proved fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579
Removal of the previous telemetry first, then count, store, declare, ask, attribute and report, opt-in and cleanup, and the end-to-end proof. Pins the consent key, the binding snapshots, the hook guards and a mutation per rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579
The previous measurement is replaced by a deterministic, declaration-attributed one in the following commits of this branch. This commit removes the old one so nothing half-describes the new one. cli: delete the telemetry context, its wiring, command, displays and tests, and everything that existed only to serve it: the measurement vocabulary, the per-tool telemetry declarations and transcript locations, the git adapter that installed the commit-trailer hook, the identity and sink resolvers, the FileWriter chmodExecutable method and the run-journal gitignore entry. The OpenCode hooks bridge no longer names the deleted plugin module in its generated comments; the framework build golden is re-baselined for that one line. Architecture guards, biome overrides, mutation scope, smoke steps and the help golden describe the CLI that remains. plugins: delete the aidd-telemetry hooks and skills (README reduced to a stub, manifest description updated). Remove the backlog-link.json step, reference and template from aidd-pm:04-spec and aidd-dev:01-plan, the backlog-link paragraph from the SDLC frame, and every aidd:step-end echo with the SDLC, async-dev and backlog "Say when this orchestration is over" sections. scripts and CI: delete the telemetry script tests, their fixtures, the two telemetry scripts, the generated prompts documentation and its lefthook job, the prepare-commit-msg trailer job, the journal replay, telemetry check and telemetry e2e exclusion in cli-ci.yml, and the identifier-join probe. Tests that used the telemetry plugin as a byte fixture read aidd-context instead. docs: delete the cost-report and metrics contracts and aidd_docs/runs/, rewrite every memory, README and docs line that claimed the previous telemetry, and mark the pending telemetry task folders of 2026_09 as superseded. The previous version's on-disk locations are recorded in aidd_docs/tasks/2026_10/2026_10_09_deterministic-tokens/context/previous-version-leftovers.md for the opt-in and forget phases. BREAKING CHANGE: the aidd telemetry commands, the aidd-telemetry hooks and skills, backlog-link.json and the AIDD-Session-Id commit trailer are removed; measurement returns, for Claude Code first, later on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Read Claude Code transcripts into one usage record per billed model call. - key message.id:requestId, or message.id:sessionId:timestamp without a request id - drop the synthetic model; isSidechain or agentId makes the call a subagent - cache_write and cache_write_1h are summed over the message iterations; the one-hour part is unknown when any iteration lacks its split - each advisor iteration is its own record, keyed <key>#advisor<n> - an unrecognised shape is reported and never guessed; an unknown counter is null, never 0 - global fold across every file: largest total, then earliest time, then lowest session id, independent of input order - source: recursive walk of the projects root including subagents and *.jsonl.superseded-* set-asides, byte-offset reads, an unterminated last line is left unconsumed, a shrunk or replaced file is re-read whole Re-adds the telemetry context to the architecture guards (public modules, biome layer overrides, codebase map, mutation scope, tool-name baseline). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Add `aidd telemetry ingest`: read what every Claude Code transcript gained and store each billed call of the projects that opted in. - ledger: <telemetry dir>/ledger/<YYYY-MM>.jsonl by the record's own UTC time; a changed partition is rewritten by temp file and rename, so a better snapshot of a call replaces the held one even across months and no reader meets two live records or a torn line - idempotent: held records fold first, so an equal record changes nothing; the phase 3 fold decides every replacement, in any order - lock: ledger/.lock holds pid and creation time, created exclusively; a dead pid or a lock older than ten minutes is cleared, a live recent one is waited for and released only by its owner - repository: git rev-parse per working directory, id = sha256 of the normalised host/owner/repo of origin (https, ssh, scp-like, credentials, port and .git dropped), else the lowest root commit; a linked worktree resolves to its main repository - consent: .aidd/config.json telemetry.enabled with version 2; the previous bare enabled is not consent; an unparseable file is counted; the main working tree answers for a worktree without its own file; AIDD_TELEMETRY=0 refuses before anything is read or written - roots.json remembers each directory seen alive, so lines from a deleted one still resolve; one never seen alive is counted, not stored - bindings/branches.jsonl snapshots branch.<name>.aiddTask|Ticket|DeclaredAt with the branch's creation time from its reflog, only when it changed - write order under the lock: ledger, roots, snapshots, offsets last - owner-only storage: 0700/0600 on POSIX, icacls on Windows - coverage counters in the ingest result, by reason not stored Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
aidd telemetry task <name> [--ticket <ref>] | --none binds the running Claude session from now on and, on a working branch, the branch itself through its git config, then snapshots the declaration at once. The default branch and a detached head are left untouched. A project that has not opted in to version 2, or AIDD_TELEMETRY=0, is refused. With no argument the command shows what the work is bound to and where that comes from. The format the CLI writes and the plugin hooks read is pinned by one shared fixture under scripts/__tests__/fixtures/telemetry-bindings/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Three Claude-only hooks, inert unless the project opted in: a prompt gate that answers a typed declaration without the model and asks once on an unbound working branch when a person is present, a session-start that keeps the task across /clear and /branch, and an async catch-up that runs the ingest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
aidd telemetry report reads new transcripts, then splits what the stored
calls consumed along total, person, session, model, day (UTC), repository,
task or ticket, four counters apart, every axis summing to the same
totals. An unknown counter is left out of its sum and counted, never added
as zero.
Attribution is one pure function: the session's declaration at the call's
time, else its carry, else the branch's declaration from the snapshots
(from the branch's creation, so declaring late moves earlier work), else
unattributed with the reason. The first declaration in a carried session
applies to the whole session. Sub-agent and advisor calls follow their
session.
aidd telemetry identity <id> writes {person_id} to identity.json in the
telemetry directory, owner-only; --off removes it. Nothing is carried over
from the previous version's file.
Ingest also reports when the oldest transcript still on disk was last
written, for the coverage line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Attribution read only the latest snapshot of a repository and branch name, so a branch deleted and created again under the same name took the older generation's calls to the new task, or to none. Snapshots are now read as generations told apart by branch_created_at: a call belongs to the youngest generation created strictly before it, and within that generation to the latest snapshot, so declaring late still moves its earlier work. A snapshot with no creation time speaks only when no dated generation matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
`on` writes consent at version 2, removes the previous version's commit hook line with its script, run journal and ignore entry, and resets the ledger offsets and remembered consent so earlier history is stored once. `off` switches off and keeps the version. `forget` previews, and with --yes erases both versions' files and declaration keys under the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Two thin skills over the CLI: 00-init opts in after consent, sets or removes the identity and forgets with a preview; 01-usage maps a question to report flags and reads the JSON without computing a figure. A test checks every command they name against the help golden. The usage contract, the plugin README and the CLI memory describe the record, the version 1 envelope, the stores and the ask and carry rules. Stale mentions of the replaced measurement go, the smoke harness covers the telemetry commands, and the bundle budget is set to the measured build plus 2 %. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Opt in, get asked on an unbound branch, declare by typing it, clear and keep the task, run a headless session, then check that every report axis adds up to the total and lands on the declared task and ticket. The real hooks run through an aidd shim, a .cmd on Windows and a node script elsewhere, with no AI tool binary on PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The built CLI and the prototype's independent reader counted the same projected history: 2,746 files, 262,328 records, 68 days, no day differing. The usage record's branch comment now says what attribution does with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
C1: partitioning is linear, an ingest rewrites only the months that changed, declarations and snapshots take a bindings lock and never wait on the ledger lock. C2: on lifts a remembered refusal for every directory of the repository. W1: ordinary prompts that look like a declaration pass the gate. W2: a branch redeclaration applies from its own time. W3: consent is the clone's git config aidd.telemetry, never committed. W4: cmd.exe /s command line, and the hook home matches the CLI's. W5: mutation proofs recorded. Minor: stale context docs, superseded old plan, report wording, dead attribution reasons, V1 config block removal, forget wording, next step. 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
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
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
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
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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
…re remembered as 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
A call is stored only if its directory was seen alive in clone X, X is the clone answering now (or the remembered X once it is gone), and X's consent covered the call's time. A clone is its git common dir's dev, ino, birth time and path; consent is a history of intervals kept beside the ledger, opened by on, closed by off or by an ingest that finds the key changed or the clone gone. roots.json becomes format 2: one entry per directory and clone, no consented flag. forget names only gone clones that had consented. 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The identity comment now cites the Node Stats documentation and the libuv sources it was read from, and drops a claim about FAT and network shares that no source gave. The proofs file states what was observed: the damaged-month mutation at its stated scope and without the new assertion, and row 12 at the old code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Where Linux refuses statx, the birth time is the change time, so a clone's identity moves with each entry added to its git dir, the key write of `on` included. `off` closed only the interval of the identity it read, and a call made after it could be stored. It now also closes the open interval its key names when that interval was opened at the same path. A copy elsewhere carries the token, not the path, and is left alone. With statx refused, 0 of 36 consent rows store more, 3 runs; with statx, 36 of 36 pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The identifier-join job lost its probe with the previous telemetry. This one runs claude -p against a dead API and judges, through the shipped hook predicates, that the hook's payload, its environment and every transcript line name one session, that CLAUDE_PID is the hook's parent, and that a headless run never reads as a person present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The pending telemetry plans of August are superseded by this one. The mutation proofs record the off fix and the probe, and the oracle note names the one guard added to the transcript reader since. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The hook takes CLAUDE_PID and falls back to its parent pid only when it is unset, so the probe now fails only when CLAUDE_PID is gone, not when a shell stands between Claude and the hook. Its git init no longer inherits GIT_* from a git hook, its temp dirs are removed, and the job runs when the probe or the hook predicates it uses change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The identifier-join probe's first CI run showed a shell between Claude and its hooks on Ubuntu: CLAUDE_PID 2565, hook parent 2588. The parent pid only stands in when CLAUDE_PID is unset; the comment now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Removing the previous telemetry's fields from the cursor profile took killed mutants with them, and the tools-cursor score fell to 96.8, under its 97 floor. The surviving mutant on the agent name's .md is real: an agent named read.md-first.md lost its inner .md. Five of the others are equivalent. Score now 97.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Observed with the maintainer: the terminal and the desktop app set ATTENDED=1 and show a block's reason; the VS Code extension sets no ATTENDED and shows neither a reason nor a systemMessage, so a block there would eat the prompt silently. The presence rule now names the two surfaces seen to show the reason, cli and claude-desktop, instead of any entrypoint not starting with sdk, which would block a surface never seen. Each condition has a mutation that turns its row red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The spec asked every surface where a person can be present, the IDE extension included. The extension shows no hook message, so it is not asked: recorded as a deviation in the spec and the plan, and the README tells a VS Code user to declare from a terminal, which binds the working branch. Three documents still stated the sdk rule. The presence matrix now judges every row before failing, so each mutation names all the rows it turns red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
IDE extensions in general, not VS Code alone: the rule asks only the terminal and the desktop app, and VS Code is the extension observed. The README section becomes a table and two bullets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Ingest held Claude's reader directly. A reader is now a tool's transcripts and its line format, and ingest reads a list of them: a second tool is its format, its source and one wiring line. Each reader hands back its own files' positions, laid over the ones held, so no tool undoes another's and a file no longer listed keeps its place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
`aidd telemetry status` says whether this clone is measured, since when, and the task its work is bound to; not measured is an answer, exit 0. `task` with no name used to show that and now refuses, pointing at status, so each command does one thing. Bare `aidd telemetry` already prints the group's help. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
With `task` declaring only, a bare `aidd telemetry task` typed as a prompt would have run the CLI to be refused. The hook now treats it as a declaration it does not understand and shows the usage, without running anything. The end-to-end checks of what work is bound to now go through `status`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Conflicts resolved: - lefthook.yml: next's one-line catalog job (#998) kept; the telemetry prompts-doc job and the prepare-commit-msg trailer job stay removed, with the trailer delegate script next added for them. - cli/tests/e2e/helpers.ts: both sides' imports. - README.md and architecture-rules.test.js: counts measured on the merged tree, 51 skills and 48 skills with actions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
…d a tool The privacy contract and the CLI memory listed the commands without status. The memory now says what adding a tool takes: its format, its source, one reader in the wiring and its name in USAGE_TOOLS. The two-reader ingest test gives the second reader another tool's name, and the not-measured lines all start the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The smoke script lists every command the binary has and fails on a drift; status was missing, and is now run after a declaration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
This was referenced Oct 12, 2026
Tried in a real session: the ask was five lines, and the confirmation of a declaration showed the CLI's whole output inside Claude's "blocked by hook" frame, which read as still blocked. The ask is now one line naming the one way that always works, the declaration typed as the next prompt: a terminal's `!` runs whatever `aidd` its PATH holds, not the one the hook found. A declaration is confirmed in one line that says to send the prompt again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
A first ingest after `on` read the whole Claude history and asked git the repository of every old call, though none could be stored: over 22 minutes on a 6.2 GB history, never finished. A transcript last written more than a day before the first consent interval opened is now not read, and its position is set to its last complete line, so what it gains later is read. The day absorbs a file clock that lags the call clock. Same history: 246 s for the first ingest, 1.1 s for the next. What is stored does not change: a test ingests the same transcripts with and without the skip and stores the same records, and on a real session the stored calls match an independent count (5 calls, 217,337 tokens). Pre-consent calls are no longer counted under a reason, and forget may leave a hand-set key in a clone that never ran `on`; the contract says both. 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
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Conflicts resolved: - cli-ci.yml: next's Windows architecture step kept; the commit trailer end-to-end it named stays removed with the previous telemetry. - runtime/wiring/framework.ts: next's recipe validation kept; the previous telemetry's spread stays removed, telemetry is wired whole. - bundle budget: measured on the merged tree, 675.2 KB, budget 689 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Brings #1003, which restores Stryker's per-test filtering under Vitest 5 (#912): without it, mutants survive with no test run, and the telemetry and presentation scores fell under their floors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The earliest interval of any clone decides, a damaged consent log reads everything so each call keeps its reason, and a read that was not skipped says so. The first interval is now Math.min of every start, infinity when there is none, with no separate empty case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
The presentation mutation score fell to 94.0 once next's recipe validation joined this branch's telemetry commands, whose descriptions no test of that scope read. Each subcommand's help is now compared with the help golden, and four commands are shown to report a failure and exit 1. Score 95.1 against a floor of 95. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
With no clone opted in, ingest reads no transcript, so every count is zero and the usage skill could not say why: it now names that case. A test chains no consent, `on`, then a grown transcript, and only the new line is read. The contract states a network file clock lagging by over a day and transcripts copied from another machine, and the hook's refusal of unsafe characters says ASCII. 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
Brings the sharded mutation CI (#1007). Conflict: the telemetry scope keeps this branch's globs and floor (96) and takes next's 3 shards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
This was referenced Oct 12, 2026
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
Replaces the previous telemetry with a deterministic token count for Claude Code. Every billed call is counted exactly once and stored locally. The report breaks the calls down by person, session, model, day, repository, task and ticket, and every axis sums to the same total. The task comes only from an explicit declaration, and nothing is sent anywhere.
Contract: spec.md. Plan: plan.md. Product contract: usage-contract.md.
🛠️ How it works
message.id:requestIdand duplicates are folded globally: the largest total wins, then the earliest time, then the lowest session id. Synthetic messages are dropped. Each advisor iteration becomes its own record. Cache writes are summed over the message iterations, and an unknown counter stays null, never 0. (contexts/telemetry/domain)aidd telemetry task <name> [--ticket <ref>] | --nonebinds the session and the working branch; branch bindings live in local git config. The plugin hooks ask once per unbound working branch, only for Claude, only when a person is present, and only where consent is open. A declaration typed as a prompt is parsed by a strict tokenizer and run without a shell, so it never reaches the model. A/clearor/branchkeeps the task. (hooks)aidd telemetry onwritesaidd.telemetry=2:<token>to local git config and opens an interval. A call is stored only if its clone had an open interval at the call's time. A clone is identified by its common dir's dev, ino, birth time and realpath. There is no backfill beforeon; that is a user decision, because backfill was the source of every consent hole the reviews found. Nothing is committed, so a teammate is never measured without runningon.aidd telemetry statusshows whether this clone is measured, since when, and its task.onis not read: on a real 6.2 GB history the first ingest goes from over 22 minutes (never finished) to 246 s, the next takes 1.1 s, and the stored calls match an independent count (5 calls, 217,337 tokens).offcloses the interval.forgetpreviews first; with--yesit erases both versions' data and the git keys. The previous version's leftovers are cleaned onon.🧪 How to verify
cd cli && pnpm test(6,448 tests),pnpm test:mutation:telemetry(96.2, floor 96),bash scripts/smoke-tools.sh;node --test 'scripts/__tests__/**/*.test.js'(434).RUNNER~1) was not tried, and neither was thecmd /slaunch of an npm shim on a real install.git configoff is not seen if no hook or ingest runs before the clone is deleted;statx(seccomp, kernel before 4.11), the birth time Node reports is the change time, so the identity moves at each entry added to the git dir. The clone stops being measured;offalso closes the interval its key names at its path, so it fails closed. Simulated (statxmade to fail understrace): 0 of 36 rows stored more, 3 runs; before that change one row stored a call made afteroff.identifier-joinruns the installedclaude -pagainst a dead API and checks, through the shipped hook predicates, that the hook's payload, its environment and every transcript line name one session, thatCLAUDE_PIDis set, and that a headless run never reads as a person present (probe-identifier-join.cjs). It holds on 2.1.295.✅ I certify
🤖 Generated with Claude Code
https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp