Conversation
A Grok agent ran fine and cost $0.00 forever. The telemetry env that makes an agent push OTel is injected for Claude Code alone (hive.ts `ensureAgent`), and a Grok agent writes no Claude transcript, so both of the collector's sources came back empty — no fleet numbers, and no row in cost-ledger.jsonl at all. On this machine that is 21,542 ledger rows, every one of them a Claude model, and none for the Grok agent that had been working alongside them. Its real numbers were on disk the whole time. The Grok CLI keeps a cumulative per-session snapshot at ~/.grok/sessions/<encodeURIComponent(cwd)>/<id>/ usage.json, and both halves of that path are already resolved: the Grok hook bridge normalizes its camelCase payload to `session_id` (GROK_HOOK_SHIM), which `recordSession` stores, so resolveCwd/resolveSessionId answer for a Grok agent exactly as they do for a Claude one. So this adds a reader, not any plumbing. Cost comes from the file too — `costUsdTicks`, 10^10 ticks to the dollar per Grok's own docs — so there are no Grok prices to maintain here. The dedup needs care. `appendCostLedger` is fed a running total, so re-appending an unchanged sample writes the same row forever; that is chaitanyagiri#56, which left 2,417 identical rows. The transcript fallback dodges it by reporting an empty session id to disqualify itself from the ledger, but this provider cannot — it needs a real session id to be accounted at all. Hence CumulativeSampleGate: admit a row only when the numbers move. An idle Grok agent re-reads the same file every beat and is correctly silent. `ts` is not in the signature, because when only the write time changed there is no new cost to record. The Claude path is untouched. `grokFallback` returns null for a Claude agent — its session id is not a directory under ~/.grok/sessions — and the ledger gate short-circuits on provider before the gate is ever consulted. Closes chaitanyagiri#535 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSioheUKVkWkZ3aRxjYnaJ
Contributor
🚫 This PR is missing its before/after evidenceEvery pull request here has to show its work. Screenshots or a short screen recording, before the change and after it.
How to fix it: edit the description, keep the A bug fix with no visible surface still needs it: show the failing behaviour, then the same steps passing. A terminal recording is fine. Genuinely nothing to show — a CI tweak, a typo, a dependency bump? A maintainer can apply the |
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
A Grok-backed agent ran normally and cost
$0.00 / 0 tokforever. The telemetry env that makes an agent push OTel is injected for Claude Code alone (hive.tsensureAgent), and a Grok agent writes no Claude transcript — so both ofTelemetryCollector's usage sources came back empty. The result was no fleet numbers and no row incost-ledger.jsonlat all: on my machine, 21,542 ledger rows, every one a Claude model, and none for the Grok agent that had been working alongside them.Its real numbers were on disk the whole time. The Grok CLI keeps a cumulative per-session snapshot at
~/.grok/sessions/<encodeURIComponent(cwd)>/<sessionId>/usage.json, and both halves of that path are already resolved — the Grok hook bridge normalizes its camelCase payload tosession_id(GROK_HOOK_SHIM),recordSessionstores it, soresolveCwd/resolveSessionIdanswer for a Grok agent exactly as they do for a Claude one. This adds a reader, not new plumbing. Cost comes from the same file (costUsdTicks, 10^10 ticks to the dollar per Grok's docs), so there are no Grok prices to maintain.The dedup is the part that needed care.
appendCostLedgeris fed a running total, so re-appending an unchanged sample writes the same row forever — that is #56, which left 2,417 identical rows. The transcript fallback dodges it by reporting an emptysessionIdto disqualify itself from the ledger; this provider cannot, because it needs a real session id to be accounted at all. HenceCumulativeSampleGate: admit a row only when the numbers move.The Claude path is untouched.
grokFallbackreturnsnullfor a Claude agent (its session id is not a directory under~/.grok/sessions), and the ledger gate short-circuits on provider before the gate is consulted.Closes #535
Type of change
Evidence
No visible UI to capture here — this is a main-process ingest path — so per the template's "no visible UI" rule the evidence is a red→green test plus a ledger diff taken against a real Grok agent.
Before
test/grok-usage-ledger.test.cjsonmain, with the fix reverted:(3 and 4 pass on
maintoo — they assert that a non-Grok agent gets nothing from this source, which was already true and must stay true.)And the real ledger, against the live Grok agent on this machine:
After
Same test file, same command, with the change applied:
Same agent, same
cost-ledger.jsonl, with the fix — the row that now gets written, and the dedup holding across three idle beats:$0.5388iscostUsdTicks: 5388341200 / 1e10. Reading that number raw would have billed the session at $5.4 billion, which is why the tick scale has its own assertion.How I tested it
npm run typecheck— clean.npm run test:focused— 840/840 pass, including the 6 new ones.npm run build— succeeds.test/grok-usage-ledger.test.cjsfirst and confirmed it goes red onmain(4 of 6 failing) before the fix existed, then green with it — output above. It sandboxesHOMEto a temp dir, so it never reads a developer's real~/.grok.ryan-mu1mcvvx) and its real~/.grok/sessions/.../usage.json, and confirmed the row it produces matches the file, and that three consecutive idle beats append once rather than three times.turns[]grew from three entries to four, and the new entry'sendedAt(01:43:56.258856Z) equals the file'supdatedAtand sits within a millisecond of its mtime — so the snapshot is rewritten at turn end, one entry per turn. The session totals moved with it ($0.5388 → $0.8907), which is exactly the change the dedup gate is there to admit.Credit (optional)
Discord:
X:
Checklist
npm run typecheckpasses.npm run test:focusedpasses.npm run buildsucceeds.commented-out code, or unrelated formatting churn in it.
DESIGN.md/tokens.ts— no ad-hoc colors,spacing, or fonts. (No UI in this change.)
ATTRIBUTION.md. (No art.)🤖 Generated with Claude Code
https://claude.ai/code/session_01YSioheUKVkWkZ3aRxjYnaJ