Skip to content

fix(web): preserve user-authored text during prompt recall #10056

Description

@saphid

Prompt recall can change text that the user actually typed when it resembles content appended by the app. This is an existing main behavior from #9173, verified while reviewing the main-to-V2 reconciliation #10051; it is not caused by the V2 merge.

recallableComposerPrompt in apps/web/src/components/chat/composerPromptHistory.ts strips an Ultrathink:\n prefix and trailing <review_comment>…</review_comment> blocks, and omits text matching the attachment-only bootstrap or plan-implementation prefix, using text matching without send-time provenance. A user-authored prompt with those same strings is indistinguishable from generated context.

Reproduction: send literal text Ultrathink: followed by a newline and Keep this prefix in my example. With an empty composer, ArrowUp recalls only Keep this prefix in my example. A literal trailing review-comment example can similarly disappear. This is source-verified in main 6270a6f88 and reconciliation da3cf59dd; the helper file is identical on those revisions. No OS-level keyboard reproduction was performed for this edge case.

Expected: recall preserves user-authored text byte-for-byte while excluding context that the application added at send time. Use reliable provenance or another representation that distinguishes the two; avoid inferring origin solely from matching text.

Acceptance:

  • Literal user-authored prefixes and trailing blocks survive recall.
  • Actually generated context remains excluded.
  • Regression coverage distinguishes identical text with different origins and preserves normal history navigation.

Verified P2 follow-up; the V2 reconciliation preserves existing main behavior and does not claim this fixed. Checked existing prompt-history issue searches and #9173 before filing.

Reported through the OpenAI Codex harness by GPT-6 Astra.

Activity

  1. juliusmarminge commented on Sep 5, 2026

    @juliusmarminge
    Member

    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):

    1. Drops a leading Ultrathink:\n (CLAUDE_ULTRATHINK_PREFIX).
    2. Drops a trailing run of <review_comment>…</review_comment> blocks (stripTrailingReviewComments).
    3. Then strips trailing preview / element / terminal context the same way.
    4. Returns "" (entry omitted) when the remainder equals ATTACHMENT_ONLY_BOOTSTRAP_PROMPT or starts with PLEASE 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)

    1. Send exactly:

      Ultrathink:
      Keep this prefix in my example
      
    2. Clear the composer.

    3. 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

    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.

  2. added
    via-triageFiled through npx t3 triage
    bugSomething is broken or behaving incorrectly.
    on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions