Skip to content

fix(native-chat): preserve queued Claude prompts - #16121

Closed
brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/sta-4146-native-chat-prompt-loss
Closed

brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/sta-4146-native-chat-prompt-loss

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​310 $\color{#cf222e}{\Huge{\mathbf{−}}}$​10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​300
Prod 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​318 $\color{#cf222e}{\Huge{\mathbf{−}}}$​231 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​87

ELI5

Claude is in the middle of answering and you type another prompt. Claude Code accepts it, queues it,
and answers it — you can watch that happen in the terminal. But Orca's chat view never showed the
prompt you typed. You were left reading a reply with no question above it, or (worse) the prompt
turned up in the wrong place, above a reply it actually came after. Same conversation, two different
stories depending on which pane you looked at.

This makes Orca record a queued prompt as a real turn in the conversation, in the position the
terminal actually put it.

User-facing before / after

Before After
You send a follow-up while Claude is still working, then look at the chat view Your prompt was missing entirely — an answer appeared with nothing above it explaining what it was answering Your prompt shows up as your turn, where it belongs
Same thing, scrolling back through the transcript later The queued prompt could appear above a reply that came before it, so the chat read in a different order than the terminal Both panes show the same order
You scroll back through older history, or watch new messages arrive live The missing prompt was missing everywhere It's there in both the paged history and the live view
Your phone or web client is a different version than the host — Older clients see exactly what they saw before; the new turn is only sent to clients that say they understand it

When it bites

Anyone using Claude in Orca's chat view who sends a follow-up while it is still working — which is
one of the most ordinary things there is to do.

Summary

  • Decode Claude queued_command prompt attachments as persisted user turns instead of dropping them.
  • Preserve the queued turn's transcript position without rewriting its provider timestamp.
  • Capability-gate the newly surfaced row before pagination and live-batch counting so independently updated hosts and clients remain compatible.
  • Advertise support from desktop runtime, web, and mobile clients; mobile continues to preserve host arrival order.

Supersedes #14309

  1. Derives a collision-free collection order from transcript position instead of mutating timestamps by +1ms.
  2. Uses one shared renderer ordering invariant across full assembly, incremental assembly, and final render grouping.
  3. Applies mixed-version capability projection before bounded reads and 40-message live batches, so legacy clients keep full windows and unchanged behavior.
  4. Preserves the original enqueue timestamp and covers pending-echo retirement, pagination, live tailing, desktop/web transport, and mobile arrival order.

Verification

  • Acceptance test failed before the fix because queued prompt was absent, then passed after implementation.
  • Decoder mutation proof failed with the prompt absent.
  • Ordering mutation proof failed with queued prompt rendered before first reply.
  • pnpm vitest run --config config/vitest.config.ts <10 affected files> — 190 tests passed.
  • Mobile Vitest — 34 tests passed.
  • pnpm typecheck.
  • node config/scripts/check-changed-code-quality.mjs — 0 findings.
  • node config/scripts/check-react-doctor-changed.mjs — 0 issues.
  • oxfmt check passed; mobile/pnpm-lock.yaml is unchanged.
  • Electron Playwright CDP: branch identity verified; the rendered Native Chat surface showed first prompt → first reply → queued prompt → final reply, each exactly once.

Fixes #14308
Linear: STA-4146
Author: @BrennanKB5

Co-authored-by: Dalton Haksu Lee <lulumeya@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b3360afe-b475-40d5-8a8a-33d913c5f445

📥 Commits

Reviewing files that changed from the base of the PR and between ee1e002 and ba068ab.

📒 Files selected for processing (22)
  • mobile/src/session/use-mobile-native-chat-session.test.ts
  • mobile/src/session/use-mobile-native-chat-session.ts
  • src/main/native-chat/transcript-line-decoders-claude.ts
  • src/main/native-chat/transcript-line-decoders.claude.test.ts
  • src/main/native-chat/transcript-reader.test.ts
  • src/main/native-chat/transcript-tail-reader.ts
  • src/main/native-chat/transcript-watch-contract.ts
  • src/main/native-chat/transcript-watch.ts
  • src/main/runtime/rpc/methods/native-chat-client-transcript-window.ts
  • src/main/runtime/rpc/methods/native-chat.test.ts
  • src/main/runtime/rpc/methods/native-chat.ts
  • src/renderer/src/components/native-chat/native-chat-incremental-assembler.test.ts
  • src/renderer/src/components/native-chat/native-chat-incremental-assembler.ts
  • src/renderer/src/components/native-chat/native-chat-message-grouping.test.ts
  • src/renderer/src/components/native-chat/native-chat-message-grouping.ts
  • src/renderer/src/components/native-chat/native-chat-message-order.ts
  • src/renderer/src/components/native-chat/native-chat-pending.test.ts
  • src/renderer/src/components/native-chat/native-chat-session-assembler.ts
  • src/renderer/src/components/native-chat/native-chat-session-transport.test.ts
  • src/renderer/src/components/native-chat/native-chat-session-transport.ts
  • src/renderer/src/web/preload-api/web-native-chat-api.ts
  • src/shared/native-chat-types.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/main/native-chat/transcript-line-decoders.claude.test.ts
  • mobile/src/session/use-mobile-native-chat-session.test.ts
  • src/shared/native-chat-types.ts
  • src/main/native-chat/transcript-line-decoders-claude.ts
  • src/renderer/src/components/native-chat/native-chat-pending.test.ts
  • src/renderer/src/components/native-chat/native-chat-session-transport.test.ts
  • src/main/native-chat/transcript-watch-contract.ts
  • src/renderer/src/components/native-chat/native-chat-message-order.ts
  • src/renderer/src/components/native-chat/native-chat-message-grouping.ts
  • src/main/native-chat/transcript-tail-reader.ts
  • src/renderer/src/components/native-chat/native-chat-incremental-assembler.test.ts
  • src/renderer/src/components/native-chat/native-chat-session-transport.ts
  • src/main/native-chat/transcript-watch.ts
  • src/renderer/src/components/native-chat/native-chat-message-grouping.test.ts
  • src/main/native-chat/transcript-reader.test.ts
  • mobile/src/session/use-mobile-native-chat-session.ts
  • src/renderer/src/components/native-chat/native-chat-incremental-assembler.ts
  • src/renderer/src/components/native-chat/native-chat-session-assembler.ts
  • src/main/runtime/rpc/methods/native-chat.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Claude attachment records can now decode into queued user messages. Transcript reads and subscriptions can include or suppress queued prompts through propagated request options. Native chat transcript windows now apply bounded, client-aware sanitization. Shared ordering utilities coordinate session assembly, grouping, and incremental updates so queued prompts retain their transcript positions. Tests cover decoding, filtering, RPC propagation, window behavior, rendering, and incremental ordering.

Merge Risk: 🔵 Low · up to ba068

The PR preserves queued Claude prompts across desktop, web, and mobile while retaining ordering and compatibility behavior. Merge is reasonable with explicit owner awareness that client-specific transcript limits should be confirmed to use trusted, server-controlled client classification.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 22 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation addresses issue #14308 by decoding queued prompt attachments, excluding task-notification records, preserving transcript order, reconciling pending echoes, and covering pagination a…
Out of Scope Changes check ✅ Passed The changes remain related to issue #14308. The shared ordering utility, capability gating, transcript window refactor, transport updates, and client-specific behavior support queued-prompt decoding, …
Title check ✅ Passed The title clearly and concisely describes the primary change: preserving queued Claude prompts in Native Chat.
Description check ✅ Passed The description is detailed and relevant. It explains the user impact, implementation, compatibility behavior, linked issue, and verification results. It omits several template headings and checkboxes…
Full details: Linked Issues check

Explanation

The implementation addresses issue #14308 by decoding queued prompt attachments, excluding task-notification records, preserving transcript order, reconciling pending echoes, and covering pagination and live updates with tests.

Full details: Out of Scope Changes check

Explanation

The changes remain related to issue #14308. The shared ordering utility, capability gating, transcript window refactor, transport updates, and client-specific behavior support queued-prompt decoding, ordering, compatibility, and rendering.

Full details: Description check

Explanation

The description is detailed and relevant. It explains the user impact, implementation, compatibility behavior, linked issue, and verification results. It omits several template headings and checkboxes, but the required information is mostly covered.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@choihjin

Copy link
Copy Markdown
Contributor

Ran this branch against a real reproduction of the bug it fixes, and it works — with one gap on prompts that carried images.

It fixes the stranding

I hit #14308 as a user and traced it in a live transcript. The trigger is Claude enqueueing the prompt and then absorbing it into the turn already running:

type: queue-operation | operation: enqueue | 09:43:12.919 | content: "[Image #43] …"
type: queue-operation | operation: remove  | 09:43:13.590 | reason: "absorbed_mid_turn"

In this reproduction no standalone user record was written, so the optimistic echo had nothing to match and stayed pinned until a restart cleared the renderer cache. Worth noting for the issue: the user does not have to send while it looks busy — this fired on a send that looked idle, which is probably why it reads as random.

Decode + prune, same test both sides:

decodeClaudeTranscriptLine prunePendingSends
merge base cf5e087246 null — record dropped echo survives, stranded
this branch 92530eadab role=user blocks=[text] survivors=0 — retired

Same result for a 1-image and a 2-image queued prompt, and against a real attachment line lifted from a live transcript rather than a synthetic one.

Gap: images on a queued prompt are dropped

The restored turn is text-only. Decoding the real record gives:

decoded: role=user blocks=[text]        # the prompt carried an image

claudeContentBlocks reaches imageRefBlock, which needs a url or a path:

const url = extractString(source?.url) ?? extractString(record.url)
const path = extractString(record.path)
const alt = extractString(record.alt) ?? undefined
if (!url && !path) {
  return null
}

The queued prompt I inspected carried neither. Its image source held only data, media_type and type:

prompt parts: ['text', 'image']
image source keys: ['data', 'media_type', 'type']     # no url, no path

In the non-queued shape I compared against, the image instead arrives as a separate [Image: source: /path] marker record, which does have a path. So a queued send with this inline-data shape comes back with its text restored and no attachment block at all — not even the filename chip that normally marks one, so nothing signals that an image was part of the message.

Not rare in my sample: of 170 queued_command attachments across local transcripts, 13 have an array-shaped prompt (the shape that carries images), and all 13 are commandMode: "prompt", so they pass this PR's gate and land in exactly this path. The remaining 157 are string prompts and are unaffected.

NativeChatImageRefBlock already permits a url, so the decoder could emit something without changing the shared type — but a data: URL alone would not be a complete fix, and I want to be accurate about that rather than hand you a shape that does not work. sanitizeNativeChatRpcImageBlock deliberately drops inline data URLs from RPC output, and desktop renders image-ref as a filename chip rather than the bytes, so a real fix needs a transport and rendering decision, or materialization to a loadable path. Flagging the gap rather than proposing the shape.

Happy to take the base64 branch as a follow-up if you'd like it separate from this PR.

(Adjacent but distinct: I filed #16972 / #16977 for the non-queued path, where a send with two or more images loses its attachments because the [Image: source: …] fold matched only a single-block turn.)

Ports preservesTranscriptOrder into main's split web preload API (#16181 moved
createNativeChatApi to preload-api/web-native-chat-api.ts) and unions it with
main's new capabilities.transcriptPending wire field.
Merging main's capabilities.transcriptPending with this branch's
preservesTranscriptOrder put native-chat.ts at 303/300 lines. Extract the
client windowing and sanitization helpers to a sibling module instead of
bumping the cap.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing this stale implementation; missing queued Claude prompts remain tracked in #14308. Rebuild transcript intake and ordering on the current chat code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Prompts sent while Claude is busy disappear from native chat, and reappear out of order

2 participants