Repository navigation
fix(native-chat): a message whose delivery is in doubt no longer holds every later send - #25028
brennanb2025 wants to merge 25 commits into
Conversation
…s every later send
…ue sends on their way
…-message-no-barrier # Conflicts: # src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx
…one never delivered shows from its journal row
… outbox file's line limit
…ections stay visible in their own words, and the phone keeps hiding them
…der the outbox file's line limit
…ered, ended the child or not, or proved its exit late, never holds the next message
…beside the message it replaces
…le, under the refusal notice's line limit
…on-message' into brennanb2025/in-doubt-message-no-barrier # Conflicts: # src/shared/agent-session-refusal-notice.ts # src/shared/agent-session-refusal-reason-words.ts
…on-message' into brennanb2025/in-doubt-message-no-barrier # Conflicts: # mobile/src/session/use-mobile-structured-agent-session.ts # src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx # src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.ts # src/renderer/src/components/native-chat/structured-agent-session-failed-start-elsewhere.test.ts # src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts # src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx # src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.ts # src/renderer/src/components/native-chat/use-structured-agent-session-messages.ts # src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx # src/renderer/src/components/native-chat/use-structured-agent-session.ts # src/shared/structured-agent-session-message-projection.ts # src/shared/structured-agent-session-outbox.ts
…on-message' into brennanb2025/in-doubt-message-no-barrier
…ded in doubt #24710 now draws every message the host rejected where it was rejected, in the host's words, so the journal-drawn not-delivered row, its outbox exclusion and the Retry rotation record go. Tests state which rejected message has a Retry: only a failed start no agent took, on a host that retries in place.
…on-message' into brennanb2025/in-doubt-message-no-barrier
… and the next send goes out
…on-message' into brennanb2025/in-doubt-message-no-barrier
…on-message' into brennanb2025/in-doubt-message-no-barrier
… exit lifecycle now keeps it
…on-message' into brennanb2025/in-doubt-message-no-barrier
…2025/in-doubt-message-no-barrier
…2025/in-doubt-message-no-barrier
|
Closing in favour of #25959. The problem this PR fixed: on the desktop, a message whose delivery was in doubt (the host answered "unknown", or the host recorded it as unknown) sat in the saved outbox and held every later message behind it. The chat looked frozen until the person acted on that message, and the freeze survived reopening the chat. Why #25959 replaces it: #25959 removes the desktop's saved outbox altogether. Messages go one at a time, and the host owns what it accepted. With no outbox there is nothing for an in-doubt message to hold up, and nothing to freeze across a reopen.
Follow-up, separate from the freeze: if the host keeps a message as unknown indefinitely, the phone keeps that message's id, so typing the exact same text again in that chat keeps answering "Delivery unconfirmed". This is being tracked with the send-layer work. |










Stacked on #24340 (base branch
brennanb2025/chat-start-failure-on-message). Retarget tomainonce #24340 lands, and before this is marked ready. This branch has #24340's current head merged in, so its diff against its base shows only this PR's changes: three production files insrc/shared/(structured-agent-session-outbox-reconcile.ts,structured-agent-session-send-disposition.ts, and a comment instructured-agent-session-outbox-admission.ts), plus tests.ELI5
You press Stop on a Codex chat while a follow-up message is still on its way. Codex doesn't take the Stop, so Orca ends the Codex process to stop it. Nobody can now say whether Codex read that follow-up. Today the chat then refuses to send anything else: every message you type after it waits forever, even after you quit and reopen Orca, and Retry doesn't free it.
After this change, that message stays in the chat as a normal message, and whatever you send next goes out.
What Changed
The problem: a message the host recorded as in doubt freezes every later send.
unknown): Codex may have read it, or not.structured-agent-session-outbox-reconcile.ts, theunknownbranch), and admission treats an "unconfirmed" copy as a barrier (structured-agent-session-outbox-admission.ts): nothing behind it is sent.NativeChatStructuredSessionDelivery.probe.test.tsx, "parks a host-confirmed unknown instead of probing it", whose message "must stay wedged behind the parked head".What you see now.
The mechanism. The desktop lets go of its copy once the host records the message in doubt, the same way it already lets go once the host records it delivered or rejected:
reconcileStructuredAgentSessionOutbox);disposeStructuredAgentSessionSendResult). That answer is also what frees a message whose record is older than the loaded page of history.Two exceptions keep the copy: a Retry a person already asked of that very record waits for its answer, and a replay saying the host lost the message's record (
durable_send_submission_missing) leaves the copy as the only thing showing the message, as before.What this PR no longer does. Earlier versions also drew messages rejected after hand-over from the host's record on every desktop, remembered the id a Retry replaced, and threaded the whole outbox into the transcript for that. #24710, now on main, draws every message the host rejected where it was rejected, in the host's words, and gives such a message no Retry, so none of that is needed and it is removed. Which rejected message has a Retry is now only #24340's rule: a failed start no agent took, on a host that advertises retry in place.
Why
Differences from the common pattern
Linked Issue
No issue. Found while working on #24864 (a Stop binds only the turn it actually stopped): a Codex Stop whose interrupt fails while a steered follow-up is unanswered leaves the chat unable to send.
Visual Proof
Live QA on 2026-10-05 ran the same steps on
main(e2da3a1), on this PR's current head (16ca69f) and on #24340 alone (e477d91, the base this PR stacks on). It used a hidden dev build on a second Mac with an isolated profile, and Grok drove it. In each run a Codex turn runs, a follow-up is steered into it and not echoed, Stop is pressed and the interrupt is refused, then two more messages are sent and the app is relaunched. Real Codex won't refuse an interrupt on demand, so the run used a stand-in Codex that refuses it, as real Codex can. The stand-in keeps turn and thread ids unique, as real Codex does. An earlier stand-in reused turn ids, which drew a spurious "Worked for 0s" and an empty "Subagent" row. That came from the stand-in, not from Orca.Before (
main): stuck.main, after Stop: the follow-up reads "Message delivery is unconfirmed." with Retry.main: "after stop 1" and "after stop 2" stay on "Sending…" and never reach Codex, and the composer still offers Stop.main, after a relaunch on the same profile: still stuck, with the notice, Retry and both "Sending…" messages.#24340 without this PR freezes the same way, so the fix is this PR's.
After (this PR): fixed.
This PR, after Stop: the follow-up is a plain message, with no notice and no Retry.
This PR: both later messages go to a new Codex process, and the chat and sidebar read Working. Nothing is duplicated.
This PR, after a relaunch: the chat is clean, with no notice and no Retry. The "Resume interrupted chats?" dialog on top is Orca's normal offer, because the stand-in's turn was still open when the app quit.
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
The outbox: a message recorded in doubt leaves the outbox when its record arrives (on its way, already unconfirmed, or retried before the record was seen) and when the host's answer or replay says so; the lost-record replay stays; a Retry of that same record stays for its answer; a message the host may not have still holds the queue until the host answers it.
Reopen without the record in view: a reopen whose loaded history no longer reaches the record does not hold the next send; a desktop that never loaded the record is freed by the automatic re-send's replay; a saved queue stuck behind a recorded message frees on open and the held message is never sent again.
Host-level repro against the real host, journal and Codex adapter with a fake Codex: the interrupt fails (-32603 or unanswered), Orca ends the process, the follow-up is
unknown, and the next message goes to a fresh Codex; -32600 "no active turn" keeps the child; a first close that doesn't prove the exit keeps the follow-uppendinguntil the next send proves the stop.Which rejected message has a Retry, stated by test titles (
structured-agent-session-delivery-notices.rejected-retry.test.ts): "a message the host recorded and then rejected is drawn in place with the host's words and no Retry, even on a host that retries in place"; "a failed start no agent took gets Retry in place when the host advertises retry-message", and none when it doesn't.Ablation: with the unknown-drop removed from the reconcile and the send answer, 17 tests fail across the four freeze test files, including the probe test "never probes a host-confirmed unknown, and sends what follows it".
Existing tests changed only where the unknown-drop changes the outcome: main's probe test (the parked unknown now sends what follows), the reopened mid-send Delivery test, the in-doubt delivery-notice cases (now no notice), the outbox reconcile, retry-hold and admission tests, and the outbox hook's unknown-head tests.
468 desktop/host test files (5,234 tests) and 34 phone test files pass;
tscfor node, web, cli and the phone app and the phone's test ratchet pass; full-file oxlint, anti-slop, the changed-lines quality gate and the max-lines ratchet pass.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
unknownrows and answers). An older desktop keeps today's behaviour: the queue is held until Retry, which is safe but still stuck. The phone doesn't keep an outbox, so it is unaffected.durable_send_submission_missing), the copy stays and still holds the queue, as onmain.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)