Repository navigation
fix(cursor): index replayed tool calls from full history on the checkpoint path #2910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
b5806d7
fix(cursor): index replayed tool calls from full history on the check…
lidge-jun 6bc0205
docs(devlog): record what the live runs did and did not prove
lidge-jun 3e20207
docs(devlog): label the fenced diagram and log blocks as text
lidge-jun e8814f8
test(cursor): pin the empty-knownCalls contract against a suffix fall…
lidge-jun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
devlog/_plan/260829_cursor_tool_continuation_pairing/030_phase4_final_gate.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 030 — Phase 4: final gate (independent post-merge audit) | ||
|
|
||
| Depends on: the merged change `8df7051201df09113b17da3f71ace992f001d66c` on `origin/dev` (PR #2900). | ||
| Scope: audit only. No source change is planned; a finding becomes a follow-up work-phase. | ||
|
|
||
| ## Why this phase exists | ||
|
|
||
| The goalplan's quality gate (`cxc loop validate`, schemaVersion 2) requires a recorded | ||
| `final_gate` review round before completion can be certified. It is also the honest place to | ||
| re-examine the work now that it has landed, because this unit already produced two wrong turns | ||
| that only external checks caught: | ||
|
|
||
| 1. The first implementation shape (standalone `[Tool Call]` entry) was rejected by the remote | ||
| suite's 363-B guard — my own audit had missed it (`002`). | ||
| 2. The first three live verification runs were routed to the operator's UNPATCHED proxy and | ||
| proved nothing; only checking the probe's own diagnostic log exposed it. | ||
|
|
||
| Both were caught by evidence, not by reasoning, which is the argument for one more adversarial | ||
| pass rather than declaring done. | ||
|
|
||
| ## Audit questions | ||
|
|
||
| | # | Question | How it is answered | | ||
| |---|----------|--------------------| | ||
| | A1 | Is the landed code on `dev` the code that was verified? | Compare the merge commit's file content against the verified branch head | | ||
| | A2 | Does any existing test expectation end up weakened? | `git diff` of the merge against its parent, restricted to `tests/` | | ||
| | A3 | Do the post-merge gates pass on the integrated tree? | `bun x tsc --noEmit` and `bun run test` on `ssh lidge` at the merge commit | | ||
| | A4 | Is every claim in the recorded evidence supported? | Re-read the goalplan's `capturedEvidence` against the artifacts it cites | | ||
| | A5 | Was the operator's environment left as found? | Live check of the launchd proxy and of the unpushed commit `5f4981853` | | ||
|
|
||
| ## Accept criteria | ||
|
|
||
| `c8`: a recorded `final_gate` verdict, plus post-merge gate output at the merge commit, with | ||
| no regression, no weakened expectation, and no unsupported claim. A failure here appends a | ||
| follow-up work-phase rather than being written off. | ||
|
|
82 changes: 82 additions & 0 deletions
82
...lan/260829_cursor_tool_continuation_pairing/040_phase5_checkpoint_suffix_gap.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # 040 — Phase 5: the checkpoint suffix reopened the same defect | ||
|
|
||
| Depends on: `8df7051201df09113b17da3f71ace992f001d66c` (PR #2900) and | ||
| `27c6993c5471958db97c9a4ce1dccc2f591f6094` (PR #2903), both on `origin/dev`. | ||
|
|
||
| ## What happened | ||
|
|
||
| A live re-run against merged `dev` reproduced the original symptom the unit had just closed: | ||
| 12 duplicate `command_execution` items and 5 phantom "interrupted" mentions in one | ||
| `codex exec` transcript against `cursor/grok-4.6`. | ||
|
|
||
| The fix was not wrong; it was incomplete. Every run that had verified it used | ||
| `continuationMode: "full-replay"`. The failing run used `"checkpoint"` for 13 of its 14 | ||
| requests — and the checkpoint path is a second, separate replay site. | ||
|
|
||
| ## Root cause | ||
|
|
||
| `buildPreparedCursorRunRequest` handles a stored checkpoint by replaying only the part of | ||
| history the checkpoint does not already cover: | ||
|
|
||
| ```ts | ||
| rawMessages: request.rawMessages.slice(suffixStart) | ||
| ``` | ||
|
|
||
| Both `rootPromptMessages` and `conversationTurns` then indexed tool calls from **that slice**. | ||
| A checkpoint is committed right after the assistant emits its tool call, so the cut normally | ||
| falls *between* the call and its result: the call is at `suffixStart - 1`, outside the slice. | ||
| The index came back empty, no invocation line was attached, and the result went out orphaned — | ||
| byte-for-byte the state the unit had set out to eliminate. | ||
|
|
||
| This is why the earlier verification was clean and the later run was not. Nothing about the | ||
| invocation line changed; the code path around it did. | ||
|
|
||
| ## The change | ||
|
|
||
| `toolCallsByCallId` now runs over `request.rawMessages` (full history) and the resulting map is | ||
| threaded into both replay builders as an optional `knownCalls` parameter. What gets *replayed* | ||
| is unchanged — still only the suffix — so covered messages are not re-sent. Only the lookup | ||
| widens. | ||
|
|
||
| ``` | ||
| suffixStart | ||
| │ | ||
| user ─ assistant(call) ─┤─ toolResult ─ … | ||
| └──── covered by checkpoint ────┘ └── replayed ──┘ | ||
| ▲ | ||
| └─ read for the invocation line; NOT replayed | ||
| ``` | ||
|
|
||
| Both call sites keep their previous behaviour when `knownCalls` is absent, so the full-replay | ||
| path is untouched. | ||
|
|
||
| ## Tests | ||
|
|
||
| `tests/cursor-tool-result-invocation.test.ts` gains a second describe block, driven red before | ||
| the fix was restored: | ||
|
|
||
| | Test | Red without the fix | | ||
| |------|---------------------| | ||
| | a result whose call is BEFORE the checkpoint cut still names its invocation | yes | | ||
| | the invocation line also reaches the checkpoint suffix turn step | yes | | ||
| | covered history is not replayed a second time | no — double-replay guard | | ||
| | an id reused in covered history yields no invocation line | no — ambiguity guard | | ||
| | native composer keeps checkpoint results off the root prompt | no — native-path guard | | ||
|
|
||
| Two assertions fail without the threading and pass with it; the other three are guards that | ||
| must hold either way, and they document what the widened lookup must *not* break. | ||
|
|
||
| Two shapes needed care while writing them: | ||
|
|
||
| - An empty `ConversationStateStructure` serializes to **zero bytes**, which the encoder reads as | ||
| "no checkpoint" and silently downgrades to full replay. A test seeded that way passes while | ||
| exercising the wrong branch. The helper seeds one real root blob instead. | ||
| - A turn only opens on a user message, so a suffix of just `[toolResult]` produces **no turns at | ||
| all** (measured: `turns=0`). The turn-step assertion therefore uses a suffix that also carries a | ||
| later user message, which is the shape that actually reaches that code. | ||
|
|
||
| ## Verification | ||
|
|
||
| - `bun test tests/cursor-tool-result-invocation.test.ts tests/cursor-tool-continuation.test.ts tests/cursor-blob.test.ts` — 123 pass, 0 fail. | ||
| - `bun x tsc --noEmit` — exit 0. | ||
| - Full suite on `ssh lidge`; no local full-suite run was used as a gate. | ||
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.