Repository navigation
Count a turn's step and tokens once when its Stop repeats - #2779
peyton-alt wants to merge 3 commits into
Conversation
A retried, duplicate, or concurrent Stop for a file-changing turn ran SaveStep again. Each run added the turn's tokens to the session total and the pending checkpoint window and incremented StepCount, so Cursor (whose checkpoint uses the window) committed double tokens. SaveStep now records which turn its last step came from, in the same locked mutation that counts it. It records the turn's pre-prompt nonce, which TurnStart writes before taking any session lock, so a TurnStart that times out on the lock still gets a fresh one. It also records the transcript size, for Stops whose pre-prompt state is already gone. A repeat merges its files but adds no tokens and no step, unless a commit condensed the window in between. Turns without a TurnStart grow the transcript, so they still count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4Y330425V53XVKKH7KPKTG9
There was a problem hiding this comment.
🟡 Changes recommended
Hook-bookkeeping transcript growth can bypass duplicate detection and double-count steps and tokens.
1 open finding
What changed in this PR
Prevents repeated Stop hooks from counting a file-changing turn’s steps and tokens more than once.
Changes:
- Adds turn nonces and transcript-size fallback markers for duplicate detection.
- Preserves file merging and pending steps after condensation.
- Adds unit and integration coverage and documents accounting behavior.
| File | Description |
|---|---|
| docs/architecture/sessions-and-checkpoints.md | Documents repeated-Stop accounting. |
| cmd/entire/cli/strategy/strategy.go | Adds turn identity to step context. |
| cmd/entire/cli/strategy/save_step_repeat_test.go | Tests repeat detection and condensation cases. |
| cmd/entire/cli/strategy/manual_commit_git.go | Gates step and token accumulation on repeat detection. |
| cmd/entire/cli/state.go | Generates pre-prompt turn nonces. |
| cmd/entire/cli/session/state.go | Persists the last recorded step’s markers. |
| cmd/entire/cli/lifecycle.go | Passes turn markers to SaveStep. |
| cmd/entire/cli/integration_test/turn_end_once_test.go | Tests sequential and concurrent Stop hooks. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if step.TurnNonce != "" { | ||
| return state.LastStepTurnNonce == step.TurnNonce | ||
| } | ||
| return step.TranscriptSize > 0 && state.LastStepTranscriptSize == step.TranscriptSize |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7233e16. Configure here.
| return state.LastStepTurnNonce == step.TurnNonce | ||
| } | ||
| return step.TranscriptSize > 0 && state.LastStepTranscriptSize == step.TranscriptSize | ||
| } |
There was a problem hiding this comment.
Stale nonce drops later turns
Medium Severity
isRepeatedStep treats any Stop that still carries LastStepTurnNonce as a repeat and ignores transcript growth. handleLifecycleTurnEnd loads pre-prompt state long before CleanupPrePromptState, and cleanup failure is only a warning, so a later file-changing Stop without its own TurnStart can still see the previous turn's nonce. That path is then skipped for tokens and StepCount, including an unprompted follow-up turn the change is meant to keep counting.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7233e16. Configure here.
The pre-prompt file is per session. A Stop that ran late can read the next turn's nonce, and a nonce match alone would then let it claim that turn and drop the turn's own Stop. A differing nonce still always means a new turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4Y58SKS865VHV7D4MER7SAS



https://entire.io/gh/entireio/cli/trails/1576
Bug
A Stop for a file-changing turn can reach Entire more than once: a retried hook, a duplicate delivery, or two hook processes racing. Each repeat runs SaveStep again. It adds the turn's tokens to
TokenUsageandCheckpointTokenUsageagain, and incrementsStepCountagain.Measured with the real hooks before this fix:
StepCountFix
SaveStep records which turn its last step came from, in the same locked mutation that adds the tokens and the step:
PrePromptState.TurnNonce) differs from the recorded one is always a new turn.TurnID, which is why this is not keyed onTurnID.Legitimate paths preserved
Overlap with #2722
#2722 applies a turn's tokens at the turn-end transition when the turn changes no files, gated on the ACTIVE phase. This PR covers the file-changing path, so the two do not overlap in behaviour. They do conflict textually: this PR wraps SaveStep's token block, which #2722 extracts into
applyTurnTokenUsage. Whichever merges second resolves that.Follow-up after both land: #2722's no-change path should record the same marker. Otherwise a no-change Stop followed by a file-changing repeat counts the turn twice.
Remaining gaps, unchanged from main:
Tests
TurnIDstays unrotated;TranscriptIdentifierAtStart.🤖 Generated with Claude Code