Triage
Confirmed bug on current main. Prompt recall reconstructs “what the user typed” by stripping send-time decorations from persisted message.text. Those decorations are inferred by string match only; the timeline does not record which spans the app appended.
Diagnosis
History is built on each ArrowUp/ArrowDown from timeline user messages (buildComposerPromptHistoryEntries → recallableComposerPrompt in apps/web/src/components/chat/composerPromptHistory.ts). The helper, unchanged since #9173 (fd773172e):
- Drops a leading
Ultrathink:\n(CLAUDE_ULTRATHINK_PREFIX). - Drops a trailing run of
<review_comment>…</review_comment>blocks (stripTrailingReviewComments). - Then strips trailing preview / element / terminal context the same way.
- Returns
""(entry omitted) when the remainder equalsATTACHMENT_ONLY_BOOTSTRAP_PROMPTor starts withPLEASE IMPLEMENT THIS PLAN:\n.
Send time writes a single decorated string. ChatView appends terminal, element, preview, and review-comment blocks, then formatOutgoingPrompt → applyClaudePromptEffortPrefix may add Ultrathink:\n. OrchestrationMessage stores only id, role, text, attachments, and turn timestamps — no original composer text and no send-time span list.
A user-authored prompt that begins with Ultrathink:\n or ends with a review-comment block is therefore indistinguishable from an app-generated prefix/suffix. Mid-prompt review-comment blocks already survive (covered by existing tests); a trailing user-authored block does not. Existing tests encode the strip behavior; they do not distinguish identical text with different origins.
This is the behavior #9173 shipped (“send-time appends are stripped so you get the text you typed”), implemented as a heuristic because that PR stored nothing extra. It is not caused by the V2 reconciliation. Open PR #10051 preserves this main helper and already names #10056 as the P2 follow-up.
Repro (source-verified)
Send exactly:
1 Ultrathink: 2 Keep this prefix in my example Clear the composer.
Press ArrowUp.
Recalled text is Keep this prefix in my example. A prompt whose only extra content is a trailing <review_comment>…</review_comment> block recalls without that block. Same outcome on main 09aac7156 / cited 6270a6f88; the helper file has one commit (#9173). No OS-level keyboard run in this triage.
Ultrathink: … without the newline is not stripped (startsWith("Ultrathink:\n") only). That is a prefix-shape mismatch, not provenance.
Expected
Recall should keep user-authored bytes and drop only context the app added at send time. Origin cannot be inferred from text match alone.
Acceptance (unchanged)
- Literal user-authored prefixes and trailing blocks survive recall.
- Actually generated ultrathink / review-comment / terminal / element / preview / attachment-bootstrap / plan-implementation context stays excluded.
- Tests cover identical text with different origins and leave normal history navigation intact.
Priority and fix shape
P2. Common path (real send-time appends) works. Failure is an edge case: the user typed the same strings the app injects. Timeline still shows the full sent message; workaround is copy from the thread.
A durable fix needs send-time provenance (store original composer text, or the spans the app appended) and recall from that. Tweaking the strip regexes cannot tell the two cases apart and will regress one or the other. #9173 explicitly avoided new persisted fields; that is the constraint, not a reason to close this.
Related
- #9173 — feature that introduced recall + text-only stripping (merged).
- #1777 — original history request (closed by #9173).
- #10051 — open main→V2 reconciliation; documents this as a known P2 and does not claim a fix.
Not a duplicate of #9173/#10051. Keep this issue open as the tracker. No needs-triage on the issue today.
Filed by Cursor Grok 4.6 via cloud-agent issue triage. Investigation only; no branch, commit, or PR.