Skip to content

Keep the tokens-saved counter moving in long sessions - #747

Open
anirudhkumar-nanonets wants to merge 1 commit into
mainfrom
claude/savings-counter-long-sessions-9c5908
Open

anirudhkumar-nanonets wants to merge 1 commit into
mainfrom
claude/savings-counter-long-sessions-9c5908

Conversation

@anirudhkumar-nanonets

@anirudhkumar-nanonets anirudhkumar-nanonets commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

The issue: in a long session the statusline's tokens saved and dollars saved stop moving, sometimes for days, while trail keeps handing the agent learnings.
Why: the counter only adds the savings lines it finds in a trail call's output. Learnings that arrive through the prompt hook and the file-touch hook carry the same [trail] learnings saved ≈ N line, and that copy was never counted. After the first hour a session mostly edits, tests and runs trail check, so its savings arrive by hook and the number froze.
The fix: when the prompt hook or the file-touch hook shows a learning, its savings line goes on the session's counter (addHookSavings in src/claude/session-metrics.ts). Each learning reaches a session once, so it is counted once.

Moves: share of trail's Claude Code sessions whose session_summary reports any saving (saved_tokens_bucket not 0; PostHog events in Metabase db 11, posthog_event_full_view, cli_name = 'trail', agent_host = 'claude-code', ci = 'false', last 7 days). 21.3% (4,077 of 19,106) on 2026-11-02.

How it was found

The three usual suspects (a cap on how much transcript is read, a cache keyed on something that stops changing, a total computed once) are all ruled out. I replayed the 24 local transcripts over 3 MB from the last two weeks that had a stored counter. In 21 the stored savedTokens equals the sum of savings lines in tool results exactly, so the counter was right about what it counted, and in 8 of those it never showed a saving at all while the hooks handed over 1.8 to 5.8 million tokens of learnings. One of the other three ran trail in subagents, whose transcripts are separate files. The last two each lost one trail call's savings; I didn't find why, and this PR doesn't touch it.

One 7-hour session sat at 474,532 tokens from its first minute to its end. Counting the learnings its hooks handed over, it would have ended at 5,417,031 and last moved 20 minutes before the end.

Merge order

This PR, #731 and #738 all change the if (text) writeSession(dir, id, s); line in learningsOnTouch (src/claude/hooks.ts), and #731 and #738 already conflict with each other there. Merge this one first. Each of the other two then keeps addHookSavings(s, text); as the first line inside if (text) { … } when it resolves that block. src/claude/format.ts is untouched, so this merges cleanly with #727, #729 and #739 (checked with git merge-tree).

Tests

  • test/hook-savings.test.ts (new): a learning shown on a file read, and one shown by the prompt hook, each add their saving to the session once. Both fail without the fix.
  • Full suite in a worktree: 141 failures, all from the main checkout's node_modules missing web-tree-sitter-depth (parser tests), plus one session-start test in test/hosts-trail.test.ts that fails the same way without this change. CI is the clean run.
  • trail check: no conflicts.

Watch out

  • A learning shown by a hook and again in a trail ask answer is counted twice, the same as two asks that return the same learning already are.
  • session_summary.saved_tokens_bucket now includes learnings the hooks hand over, which is what the Moves line measures.

🤖 Generated with Claude Code

@trailhq-graft

trailhq-graft Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🌱 graft blast radius

Nothing outside this diff depends on it. 1 area changed; no indexed dependents at depth 2.
Tests: 1 area updated its tests.
Tag: @shhdwi — Session Metrics

Who knows this code — 1 person across 1 area
Area Who knows it
Session Metrics · changed @shhdwi — 14 commits, last 6d ago

Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no @ has no GitHub handle in its commit email — tag them by hand, or add a .mailmap entry. A suggestion from history, not a CODEOWNERS rule.

Test signal per changed area — 1 ✓

Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.

  • ✓ Session Metrics — 2 of 3 reached · 1 test file changed here: test/hook-savings.test.ts
    • not reached: learningsOnTouch
3 test suites also reference this code

3 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.

  • test/claude-hook-timeouts.test.ts
  • test/trail-learning-on-touch.test.ts
  • test/trail-prompt-activity.test.ts

⚠️ 1 changed file not in the graph (.trail/learnings/2026-11-02-the-statusline-s-tokens-saved-counter-anirudhkumar.md) — no parser claims the extension, or the index predates the file.

graft blast · origin/main...HEAD · depth 2 · 5 changed files

Open the interactive graph → — click an area to see its dependent symbols at file:line.

github-actions Bot added a commit that referenced this pull request Nov 2, 2026
@anirudhkumar-nanonets
anirudhkumar-nanonets force-pushed the claude/savings-counter-long-sessions-9c5908 branch from 86e2c2c to 2f0a88c Compare November 2, 2026 09:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant