Repository navigation
fix(native-chat): a reply Orca cut off reads Interrupted; an agent's own crash still reads Failed - #28676
Draft
brennanb2025 wants to merge 4 commits into
Draft
fix(native-chat): a reply Orca cut off reads Interrupted; an agent's own crash still reads Failed#28676brennanb2025 wants to merge 4 commits into
brennanb2025 wants to merge 4 commits into
Conversation
…own crash still reads Failed Every status surface showed red Failed for a turn Orca quit, crashed, restarted or evicted, because the host's `interruption` verdict mapped to the failed mark. That mapping stood in for real agent crashes too, which were recorded only as `interruption`. The dead-generation settlement now records the existing `failure` outcome on the root turn a provider exit of its own cut, in the same transaction as its exit row, unless the turn already has a verdict or a person's Stop decides its end. Reopen settlement records no outcome. With crashes recorded at the source, `interruption` now displays as the muted Interrupted mark (desktop, phone, turn bar, Activity, notification).
… caused stays Interrupted The adapter publishes its outcome-less end before the host's exit settlement adds `failure` to the same turn, so the completion feed saw the turn as already settled and never announced the failure. The feed now remembers whether a settled request had a verdict: a verdict that lands later fires once, and nothing fires twice. An exit whose failure fact is `hostFault` (Orca force-closed a healthy agent) no longer records a provider failure; only a `providerExited` exit, or one with no fact, does. The exit-flow test now drives the real child-exit handler with the adapter's end published first, and the paired-client mapping test expects the new Interrupted mark.
… silent The completion feed announced a verdict recorded on an already-settled turn whenever that turn had settled without one, including a turn that was already in the conversation when the feed baselined it (a reopen, or a stale turn the next provider child settles) and whose verdict a thread resume later restores. Only a request that ran after the baseline can now await its verdict, so the provider's own crash still announces its failure once. Also moves the tests still asserting the old crash-cut mapping to Interrupted, and folds duplicate cases.
…keep test lines within production's
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
When Orca quits, crashes, restarts, or puts an idle chat to sleep in the middle of a native chat reply, the reply gets cut off. Nothing went wrong with the agent: Orca went away. Today every status surface still shows that reply as a red Failed. This PR makes it read Interrupted, the same muted mark a Stop uses. When the agent's own process really does crash while Orca is running, it still reads Failed, and now that failure is recorded as a fact instead of being guessed later.
What Changed
The problem. The worktree card, the agent row, the tab bar, the Activity page, the phone's agent list and the OS notification all turn a reply that Orca cut off into red "Failed". That happens because the shared mapping (
agentVerdictDisplayMarkinsrc/shared/agent-main-agent-verdict.ts) treats the host'sinterruptionverdict (a turn cut short that nobody asked to stop) as a fault, exactly likefailure. The mapping did this on purpose, because the sameinterruptionverdict was the only thing recorded when the agent's own process crashed mid-reply. So makinginterruptionread neutral on its own would have quietly turned real agent crashes grey too.Before, as a user:
After, as a user:
The mechanism.
structured-agent-session-child-exit.ts: not a close Orca asked for, and the exit's failure fact isproviderExitedrather thanhostFault) and a reply was still running, the dead-process settlement (structured-agent-session-dead-generation-settlement.ts) already writes, in one transaction, the "stopped while this response was in progress" row and the turn'sinterruptedend. It now also writes the existingfailureoutcome on the root turn that row reports on (providerExitFailureRevisioninstructured-agent-session-stale-turn-verdict.ts). The lifecycle staysinterrupted, since that is what the host observed. A turn that already carries a verdict keeps it, and so does a turn a person's Stop decides. Stop is checked explicitly here, because the shared Stop rule (turnEndAfterStop) leaves alone any turn that is written with a verdict already; this is the same check the host's own Stop path already makes before it writes.hostFault, so the turn reads Interrupted, not a provider failure. A reopen that finds the old process gone (settleStaleStructuredAgentSessionState) still writes no outcome. A process found missing later can't be told apart from Orca going away, so it is never called an agent crash. A reopen that can't prove the process is gone still reads "Couldn't confirm".interruptionnow maps to the muted'interrupted'mark inagentVerdictDisplayMarkand in the phone's copy of it (agentRowVerdictMark). It maps tointerruptedAfterin the turn bar (settledTurnStatusKey), to "interrupted" in the Activity titles (so it is grouped with Stops, not failures), and to "stopped" in the OS notification wording. The outcome contract comment insrc/shared/agent-turn-outcome.tsnow says afailurecan also be a provider exit the host watched.Completion feed (unread dot + notification): announced on purpose. The feed (
structured-agent-session-turn-completion-feed.ts) announces a turn that settles with a recorded outcome, and stays silent on the first look at a reopened session. On a normal crash the agent's adapter publishes its own "interrupted" end first, with no outcome, and the host's settlement addsfailureto that same turn a moment later. The feed used to treat the turn as already settled and ignore the later revision, so the failure never fired. It now remembers whether the settled turn already had a verdict: a verdict that arrives later on the same turn fires once, and nothing fires twice. Settlement on reopen records no outcome, and reopened history is already silent, so a reopen stays quiet.Why
interruptionwithout recording real crashes would hide genuine agent failures, so the two must land together.failureoutcome, the existing one-transaction settlement and the existing Stop rule carry it.Deviations from the common pattern:
Mixed versions. No wire shape changes:
failureandinterruptionare existing outcome values. An older client reading a newer host sees a watched crash as Failed, as it does today. A newer client reading an older host shows a crash that older host recorded as Interrupted, because older hosts never stored the failure. The status is muted rather than wrong, and the transcript row still says the agent stopped.Out of scope (separate PRs): the wording and tone of the "stopped while this response was in progress" row, and the desktop card and tab marks that disappear 30 minutes after the chat's last activity.
Linked Issue
Replaces part of #25670
Fixes #
Visual Proof
Pending: QA to follow
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
New exit-flow test in the existing recovered-turn harness (
structured-agent-session-recovered-turn-clock.test.ts), running the real child-exit handler, journal, status feed, host status row and completion feed, with the adapter's own outcome-less end published before the host settles (the real order): a provider exit while running reads Failed and fires exactly onefailurecompletion. A person's Stop before the exit stays their cancellation (muted Interrupted, announced once as a cancellation). An Orca-caused cut (hostFault) reads Interrupted and fires nothing. In the same harness, the existing Orca-side recovery cases now assert muted Interrupted, "Interrupted after 1s", and no completion event.I checked that the new test fails without the completion-feed change (no failure announced) and without the
hostFaultcheck (an Orca cut reads Failed).Updated existing tests that asserted the old mapping (shared verdict, turn bar, Activity, tab bar, worktree card, phone agent row, notification wording, restart and close flows). Removed three tests that only pinned "crash-cut reads failed/worked" and are now covered by the mapping tables.
pnpm test(Bun 1.4.3, as pinned) on every touched file, the completion-feed test and the paired-client mapping test: 19 files, 309 tests pass. All ofsrc/main/native-chatplussrc/renderer/src/runtime: 646 files, 5672 tests pass. Mobileviteston the touched phone files: pass (unchanged since the first round).orca-ci-checks: 21/22 pass, including both typechecks. The one failure,audit:code-quality:native, is six import-cycle warnings inmobile/src/transport/*andmobile/src/terminal/document/*, files this PR doesn't touch.AI Disclosure
Review
Agent skill upstream boundary
Notes
Security: no change. Cross-platform: no platform-specific code. SSH/remote: the verdict is recorded on the execution host that watched the exit, and readers on any client read the stored outcome. Mobile: the phone's mapping copy is updated the same way. Backwards compatibility: see "Mixed versions" above. Performance: one extra lookup over the journal items already in memory during exit settlement.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)