Skip to content

Native chat: a message is never marked not delivered after a crash, and reopening a chat stops reading the transcript - #30580

Merged
brennanb2025 merged 17 commits into
mainfrom
brennanb2025/crash-send-no-guess
Oct 24, 2026
Merged

brennanb2025 merged 17 commits into
mainfrom
brennanb2025/crash-send-no-guess

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 24 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​436 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2372 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1936
Prod 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​103 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1356 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1253

ELI5

If Orca crashed or restarted while a chat message was on its way to Claude, Orca later tried to work out whether Claude had got it by searching Claude's own log for it. When the search missed (and it often did), Orca marked the message in red "This message was not delivered. Send it again to continue." even though Claude had received it, and sending it again asked Claude the same thing twice. Orca now stops guessing: a message you sent stays a normal sent message, and the reply that the crash cut off ends the same way it does today.

What Changed

Before

  • After a crash or restart, reopening a Claude chat could mark a message Claude actually received as "not delivered" (STA-7416). Retyping it duplicated the question.
  • The same wrong mark appeared whenever the message started with a tag like <system-reminder> (STA-7453).
  • Every time a Claude chat reopened, crash or not, Orca read and parsed Claude's whole saved log of the chat (STA-7434), which is slow on long chats.

After

  • A message you sent before a crash shows as an ordinary sent message. It is never marked "not delivered" afterwards. If the agent's reply was cut off, the chat shows that the same way it does today.
  • Reopening a chat no longer reads Claude's log to guess delivery. Existing history adoption and explicit Rewind still read the exact saved history they need.
  • Main already keeps a queued message that was handed over before a crash as sent. This PR preserves that behavior and removes the later log search that could wrongly label the message not delivered.
  • A message the agent itself reports it discarded is still marked not delivered, as before. That is a report from the agent, not a guess.

Mechanism. On reopen, Orca already marks every message whose outcome it never heard as "unconfirmed" (shown as a plain sent message, never "Working", never blocking a new send), and already ends the interrupted turn in one settlement step. After that, attach used to read a window of Claude's saved log, anchored at the chat's last saved position, and matched each unconfirmed message by a fingerprint of its text. A message missing from that window was marked rejected "not delivered", and a message found there was marked accepted. This PR deletes that second step and everything it needed: the window reader, the log branch-proof code it ran on, the matcher, the per-agent history hooks (Claude, plus a stored-messages reader for OpenCode), and the attach step that called them. The unconfirmed state set at reopen is now the final answer.

Current main already keeps a queued message sent once it has been handed over, including after a crash, and lets an idle chat unload when no other work is waiting. This PR removes the later history-based delivery guess for those messages too; it no longer changes how the queue stores or settles them. A genuine later acknowledgment from the agent can still update the message’s saved identity.

Why

The bug class. Orca was deciding whether a message reached the agent from its absence in a log Orca does not own, read through a window whose anchor moves every turn and through text filters. Missing from that window is never proof the message wasn't delivered. Every anchor move, filter, compaction, fork, rewind or log-format change turned into a wrong "not delivered" that invites a duplicate question. STA-7416 and STA-7453 are two samples of that one cause, and STA-7434 is its cost: the read had to run on every reopen because nothing could cheaply tell whether there was anything to decide. Five earlier changes patched the same window.

Why delete rather than fix the window. The common pattern never infers delivery after a crash. The user's message stays as sent, unfinished work is cancelled or interrupted because the process ended, and a message is marked rejected only when the agent or runtime explicitly refuses it. An unknown outcome records nothing. Fixing the window (anchor at the oldest unconfirmed message, keep tagged prompts, skip the read when nothing is unconfirmed) would fix these two cases but keep the assumption that absence is proof, so the next anchor or format change would bring the same bug back. The only thing the guess bought was a red line on a message that truly never arrived. In that case the cut-off turn already shows no reply, and the user sends again.

Differences from the common pattern, all intended:

  • Explicit live refusals stay per message, the same as an explicit rejection event.
  • There is no automatic "continue" and no new retry button. Those would be new features, outside this fix.
  • The attach result still carries its list of unconfirmed message ids (no current client reads it), so older paired clients and servers keep parsing it unchanged.
  • Messages an earlier build already marked "not delivered" stay as saved. Saved history isn't rewritten automatically.
  • Copy to CLI can refuse a chat containing only a first send cut off by a crash, until a later send is accepted or the agent supplies usable saved content. This is an intended crash edge of removing delivery guessing; ordinary completed chats still copy.
  • Rewind is withheld when the loaded saved submission has no usable native boundary. Main sometimes enabled Rewind on a recovered Claude prompt by inferring that boundary from a log match. This crash-only difference is intended. Earlier known targets remain available, but can remove more history; accepted aliases, imported history and earlier forks retain their existing support.
  • If an independently updated host supplies a message without its submission facts, the client keeps its provisional Rewind offer and the host validates the cut. Missing client facts do not become a new refusal.

Compatibility. No new saved field, no wire change, no capability. A host on this build publishes no new state, only fewer narrowed answers. Older clients already render an unconfirmed recovered message today: every Codex and Pi crash produces one, and so does every Claude message the guess couldn't decide. Works the same over SSH, on remote and paired servers, on mobile and on all three desktop platforms, since nothing here is platform-specific.

Smaller side effects, matching Codex chats today:

  • After a crash, an orchestration worker's "you have mail" nudge may be sent again; the nudge only points at the mailbox.
  • A worker dispatch whose instructions were cut off reports "unknown, go look" instead of a guessed answer.
  • Queued cards paused by Stop stay paused after a crash until you Resume or the next message is accepted.

Intended crash edge: If a chat contains only a first send cut off by a crash and no reply, Copy to CLI can refuse even when the agent saved that message. The existing check requires an accepted send or an agent reply; Orca no longer guesses that acceptance from saved logs. Ordinary completed chats still copy, and receiving an ordinary reply permits copying this chat. Keeping this crash edge is intended under the decision to adopt the common pattern without delivery guessing. Targeted real Codex/OpenCode crash-recovery QA and both message-menu screenshots passed on 6771709; Claude evidence carries from 15d5ab8. Review and targeted QA are complete, with final CI passing on the corrected head.

Rewind review correction: The old menu offered Rewind on any visible turn opener, including recovered prompts the host could never cut. The menu and host now share saved native identity resolution, and genuine late acknowledgments refresh the menu without a new send. No new saved field or wire requirement is added, and prepared Rewind recovery, ordinary sends and Copy to CLI keep their existing checks.

Linked Issue

Tracked in Linear: STA-7416, STA-7453, STA-7434.

Visual Proof

Codex crash recovery and a real new reply — QA head 6771709

Recovered message, one ordinary sent bubble:

Codex recovered message

A distinct new prompt received a real reply (6m14s; the cause of that latency was not established):

Codex real new reply

Rewind menus — recovered message has no offer; earlier accepted message retains it
Recovered message Earlier accepted message
Recovered message menu without Rewind Accepted message menu offering Rewind

Screenshots only; Rewind was not selected or executed.

One OpenCode entry: crash recovery and a real new reply — QA head 6771709

Recovered message, one ordinary sent bubble:

OpenCode recovered message

A distinct new prompt received a real compass reply:

OpenCode real new reply

Carried Claude upgrade/recovery proof — PR head 15d5ab8 (historical evidence)
Main-built state before upgrade Recovered on the PR build
Claude main-built state before upgrade Claude recovered message

Real subsequent replies after reopen:

Claude subsequent reply

These are retained earlier-head results, not a new Claude run on 6771709.

Testing

  • The restart-resume test now pins STA-7416: a send cut off by a restart stays unknown/recovered, is never rejected, and the chat's continuation still runs.

  • The attach-level test now checks that reopening after a crash keeps the send as sent and still lists it as unconfirmed for older peers.

  • The crash-boundary journal test checks that the message is kept once, in doubt, never re-sent and never shown as still working.

  • Deleted the tests for the window reader, branch proofs, matcher and the OpenCode stored-messages reader.

  • The Rewind regression carries a real journal through close/reopen, checks the recovered prompt has no action without a saved boundary, and restores the action after a genuine native acknowledgment while retaining earlier history. It also checks incomplete client observation remains provisional.

  • Targeted real Codex and one OpenCode entry passed on 6771709a: one actual pre-ack crash each, same-profile reopen with an ordinary single bubble and no replay, then a distinct new prompt with a real reply. Both Codex Rewind menus were captured; no Rewind action ran. Claude upgrade/recovery/reply and desktop Copy evidence carry from 15d5ab8. Phone, worker-start unknown, linked queue/idle consumers and Rewind behavior use the approved integration/renderer contracts; no new native phone or worker timing run is claimed.

  • Conflict-only main merge e2a05bfa passed the required direct lint-staged check and 7 named files / 174 tests through tc-gate. Incoming main Rewind lifetime fencing and agent changes were retained. Two unchanged types moved into the existing Rewind host module with identical runtime output. Test-only correction b04d74739f61126fc4d13fbebaa0902f2fa68599 supplies main’s required presentation callback to the existing regression fixture, with assertions and all production blobs unchanged; that regression passed again. Fresh full PR CI and aggregate verify job 120785232570 passed on this corrected head, attempt 1. All six workflows succeeded, with 25 checks SUCCESS and 16 SKIPPED, none pending or failed; Mobile, persistence and all three WSL distros passed.

  • All test-copy/driver/watcher processes stopped, ports cleared and real-config hashes stayed unchanged. Shared qa-drive was used without an override or pin. Codex’s new reply completed after 6m14s; the cause of that latency was not established.

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

The final main merge reformatted eight files the main branch had left unformatted (whitespace and separators only, done by the required pre-commit formatter); no behavior change.

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Screenshots attached: current Codex/OpenCode recovery and new replies, both Rewind menus, and retained Claude upgrade evidence
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • CI lint, typecheck, tests and packaging passed on the exact corrected head; named local contracts ran through tc-gate, and the QA build ran only in the admitted slot

Author: @BrennanKB5

…ing a chat stops reading the transcript

After a crash or restart, attach read a window of the agent's saved log and marked any
unconfirmed message missing from it as rejected "not delivered". Absence from a log Orca
does not own is not proof: the window's anchor moves every turn (STA-7416) and its text
filters drop tagged prompts (STA-7453), so messages the agent received were marked
undelivered and retyping them duplicated the question. The read also ran on every attach
(STA-7434).

Delete the inference: the provider history window and its adapter hooks (Claude window and
branch proof, OpenCode stored-messages reader), the submission matcher, and the attach step.
A send the crash boundary leaves unconfirmed stays an ordinary sent message; the interrupted
turn still ends through the existing settlement. Explicit live refusals are unchanged.

A queued card whose hand-off a restart left unconfirmed no longer counts as able to return
to the queue, so it stops keeping an idle chat loaded.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 19, 2026 05:42
@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review.

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 86b3f0ac-d175-48c2-920c-b534b1966e5e
📥 Commits

Reviewing files that changed from the base of the PR and between e5fcbea and bae3ef6.

📒 Files selected for processing: 8
  • src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-conversation-open.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-restart-interruption-test-harness.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-cut.ts
  • src/renderer/src/components/native-chat/use-native-chat-rewind.ts
  • src/shared/agent-session-rewind.ts
  • tests/e2e/native-chat-rewind-boundary.unit.test.ts
💤 Files with no reviewable changes: 2
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-journal/journal-restart-reconciliation.ts
🚧 Files skipped from review as they are similar to previous changes: 1
  • src/main/native-chat/agent-session-wire/structured-agent-session-conversation-open.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Walkthrough
📝 Walkthrough

The PR removes provider-history readers for ACP, OpenCode, and Claude, and removes history-window APIs and restart reconciliation from journal attachment. Interrupted submissions remain unknown and recovered instead of being reconciled against provider history. Rewind-key mapping is centralized in savedRewindKeys; rewind eligibility and cuts use the shared mapping, with tests covering recovered submissions and late acceptance.

Priority: Normal

Estimated code review effort:

Merge Risk: Minimal · up to bae3e

No merge-blocking issue is established. The reported tests support proceeding with normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 27 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly summarizes both primary changes: preventing crash-time messages from being marked not delivered and stopping transcript reads when reopening chats.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The pull request description is substantially complete. It includes ELI5, What Changed, Why, linked issue references, visual proof, testing details, review notes, compatibility considerations, and che…
Full details: Description check

Explanation

The pull request description is substantially complete. It includes ELI5, What Changed, Why, linked issue references, visual proof, testing details, review notes, compatibility considerations, and checklist items. The author documents the intended behavior changes and known edge cases. The template's Fixes # field is not used because the description provides Linear issue references, and the manual-testing checkbox is not checked, but targeted QA and CI results are documented.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

After a crash during a chat send, Orca could search an incomplete agent log and incorrectly label a received message “not delivered.” Sending it again could repeat the request.

Recovered messages now remain ordinary sent bubbles. Reopening neither resends them nor guesses delivery from the agent log; the interrupted reply settles separately. Explicit live refusals and previously saved rejection records retain their behavior.

Full review: round 1. The source, whole-mechanism comparison with the common pattern, readiness and performance review passed; obsolete fixtures and the saved Rewind boundary were fixed. Intended edges remain: Copy to CLI refuses a crash-cut first-send-only chat until saved content is established; a recovered message without a saved native boundary has no Rewind offer, while earlier accepted targets remain; an older peer that omits submission facts retains a provisional client offer with host validation. Pre-existing queue-sweep cost belongs to the send-lifecycle lane. ACP’s earlier local acceptance remains distinct from provider acknowledgment; provider receipt is never inferred here.

Verified for real on 6771709a01bd4bcde38d9e8b803a27b78a537db8: Codex and one OpenCode launch entry each had one genuine send cut after recorded handover and before local acknowledgment, then reopened as one ordinary bubble with no redispatch or lingering Working state. Each accepted a distinct new prompt and produced a substantive provider reply. Both actual Codex message menus were captured: recovered has no Rewind; an earlier accepted message offers it. The Codex reply took 6m14s; its cause was not established, and the preserved stale driver summary was corrected using the actual screenshot and journal. All app/driver/watcher processes stopped, assigned ports cleared and real-config hashes stayed unchanged.

Claude upgrade, recovery, subsequent real replies, saved options and desktop Copy evidence carry from 15d5ab8afaec67e185031c98429400405646a0cf. Phone recovery/reply and strict Copy actions, worker-start unknown/no replay, and linked recovered queue/idle/history-reopen consumers are covered by the approved integration/renderer tests with synthetic transport/provider seams. No new native-phone pairing/send/Copy run, worker-start timing cut, live Rewind execution, fresh Claude run or second OpenCode entry is claimed.

Verified current head b04d74739f61126fc4d13fbebaa0902f2fa68599: full PR CI and aggregate verify passed, attempt 1. All six workflows succeeded: 25 checks SUCCESS / 16 SKIPPED, none pending or failed; Mobile, persistence and all three WSL distros passed. The post-QA merge passed 7 named files / 174 tests through tc-gate; the existing boundary regression passed again after its test-only callback correction. Worktree/index clean, mergeable/CLEAN, out of draft.

Evidence carry and changes after the carried Claude run

Main merges e5fcbea6, bae3ef6e, ba82b504, 360e36f8, 231c141c and e2a05bfa retain runtime evidence under the main-merge rule. Non-shipped/tests/lock corrections are 580b6194, 10c31feb, 4a5b28da, 6771709a and b04d7473. The PR-owned tests-row Rewind change 9a21339b is covered by the saved-boundary regression and the new menu screenshots above.

Post-QA merge e2a05bfa970ac674c353cd6d16d7701ac2585290 preserves the recovery deletions and saved-boundary eligibility, includes main’s Rewind presentation-lifetime fencing and incoming agent changes, and moves two unchanged types to the existing Rewind host module to satisfy the line limit. The type move emits identical runtime code. Correction b04d74739f61126fc4d13fbebaa0902f2fa68599 adds only main’s required presentation callback to the existing regression fixture; assertions and all production blobs are unchanged. No merge regression was found. QA remains on 6771709a; this comment reviews the corrected current head.

For Brennan’s review

Codex crash recovery and a real new reply — QA head 6771709

Recovered message, one ordinary sent bubble:

Codex recovered message

A distinct new prompt received a real reply (6m14s; the cause of that latency was not established):

Codex real new reply

Rewind menus — recovered message has no offer; earlier accepted message retains it
Recovered message Earlier accepted message
Recovered message menu without Rewind Accepted message menu offering Rewind

Screenshots only; Rewind was not selected or executed.

One OpenCode entry: crash recovery and a real new reply — QA head 6771709

Recovered message, one ordinary sent bubble:

OpenCode recovered message

A distinct new prompt received a real compass reply:

OpenCode real new reply

Carried Claude upgrade/recovery proof — PR head 15d5ab8 (historical evidence)
Main-built state before upgrade Recovered on the PR build
Claude main-built state before upgrade Claude recovered message

Real subsequent replies after reopen:

Claude subsequent reply

These are retained earlier-head results, not a new Claude run on 6771709.

Three source spots: reopen preserves the unknown outcome, saved Rewind identities, strict Copy content check.

@brennanb2025
brennanb2025 merged commit 6059a5f into main Oct 24, 2026
41 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/crash-send-no-guess branch October 24, 2026 07:30
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.

1 participant