Repository navigation
lifecycle: keep a no-change turn's tokens for every agent - #2722
Merged
Merged
Conversation
A turn-end that changes no files skips SaveStep, and that branch only recorded out-of-band usage (Antigravity). The hook-reported usage on the event (Cursor's stop payload) was dropped. Cursor's transcript has no usage fields, so condensation could not recover it either: the turn was lost from the session total and from the next checkpoint. Record event.TokenUsage on that branch, falling back to out-of-band usage only when the hook reported none, the same precedence the SaveStep path uses, so no turn is counted twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4HPW4188RCPJJK89HX2XPNM
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The fix matches existing token-source precedence and is covered by focused end-to-end regression testing.
0 open findings
What changed in this PR
Fixes lost Cursor token usage when a turn changes no files by preserving hook-reported usage in session and checkpoint accounting.
Changes:
- Uses hook-reported usage before out-of-band fallback on checkpoint-less turns.
- Adds integration coverage for session totals and checkpoint scoping across three turns.
| File | Description |
|---|---|
cmd/entire/cli/lifecycle.go |
Preserves token usage when SaveStep is skipped. |
cmd/entire/cli/integration_test/cursor_token_condensation_test.go |
Verifies no-change turn accounting and checkpoint isolation. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…files The checkpoint-less turn-end path recorded only hook-reported and out-of-band usage, so for Claude Code, Codex and the other agents whose usage lives in the transcript, a turn that changed no files never reached the session total that `entire status` shows. (The next checkpoint was unaffected: condensation recomputes it from the transcript slice.) Resolve the turn's usage on that path exactly as SaveStep does, with one helper (resolveTurnTokenUsage: hook, Codex child ledger, transcript from the turn's start offset, out-of-band), and record it through the same accounting SaveStep uses, now extracted as applyTurnTokenUsage: the session total, the pending checkpoint window, the subagent baseline rescoping, and dropping a child snapshot computed against a stale Codex ledger version. Subagent sessions of their own (Droid Workers) are skipped, as SaveStep never runs for them either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Workers A no-change turn's tokens were accumulated before the turn-end transition, with no check that the turn was still open, so a repeated, retried, or concurrent Stop added them again. A retried Claude Code Stop was worse: with the pre-prompt state already cleaned up it resolved its transcript offset from the session and re-added every earlier turn. The tokens are now applied inside the MutateSessionState that performs the ACTIVE->IDLE transition, and only while the session is ACTIVE, so the second Stop finds it IDLE and adds nothing. A Factory AI Droid Worker turn that changed no files returned through the no-changes gate before the Worker routing, so it left no task record on the parent and its transcript and tokens never reached the parent checkpoint. Workers are now resolved before that gate, and their record carries the Worker's usage computed from its whole transcript (recounted at condensation), so it lands on the parent's task once and never on the Worker's own session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4XQRQ3HNAPCABZXM4XG28D6
A multi-turn Droid Worker's record carried whole-transcript usage that condensation recounted from line 0, and condensation drops a completed record once stored, so a turn already in one checkpoint was counted again by the next. Each Worker turn now adds only its own slice (from the turn's start offset) to the record, and only when ending the Worker's own turn claims it, so a repeated Stop adds nothing; the record is no longer marked for a whole-transcript recount. Usage counted as applied only after the turn-end mutation saved, so a failed save is reported. The log for a Stop with no active turn no longer claims it was a duplicate: with no turn start there is no offset to scope the usage to, so it is not recorded in either case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4Y2P7H5M6JSVCYPKHR2MXPS
Soph
approved these changes
Oct 15, 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.
https://entire.io/gh/entireio/cli/trails/1536
Bug. When a turn changes no files, turn-end skips SaveStep. That path recorded only out-of-band usage (Antigravity), which caused two losses:
event.TokenUsagewas dropped. Cursor's transcript has no usage fields, so the turn was missing from both the session total and the next checkpoint.entire status). Their checkpoints were unaffected, because condensation recomputes them from the transcript slice.A Factory AI Droid Worker turn that changed no files also returned before the Worker routing, so it left no task record on the parent, and its transcript and tokens never reached the parent checkpoint. This one predates the PR.
Fix.
resolveTurnTokenUsageis now the single resolver for both turn-end paths. Precedence: hook-reported, then Codex's subagent ledger, then the transcript from the turn's start offset, then out-of-band.applyTurnTokenUsage. It covers the session total, the pending checkpoint usage, the subagent baseline rescoping, and dropping a child snapshot computed against a stale Codex ledger version. SaveStep andApplyTurnTokenUsageboth use it.MutateSessionStatethat moves the session from ACTIVE to IDLE, and only while it is ACTIVE. A repeated, retried or concurrent Stop for the same turn finds it IDLE and adds nothing. Without this, a retried Claude Code Stop, which runs after the pre-prompt state is cleaned up, re-added every earlier turn.No checkpoint double-counts the turn. For transcript agents, condensation still takes the main counters from the transcript slice, and the pending checkpoint usage is only a fallback.
Not in this PR. SaveStep (the file-changing path) has the same repeated-Stop exposure, which predates this PR: a retried Stop adds the tokens again and also records an extra step. The phase gate does not carry over, because SaveStep also legitimately runs on a non-ACTIVE session, for example the first Stop after Entire is enabled mid-session, which initializes state as IDLE. Fixing it needs a per-turn step marker, so it is a follow-up.
Tests.
task.jsonusage in the first checkpoint, that the Worker's own session records none, and that the second checkpoint's task holds only the second turn. It fails before this change.🤖 Generated with Claude Code