Skip to content

fix(native-chat): show a reply cut off by an Orca crash or quit like a finished turn, with one explanation - #25043

Merged
brennanb2025 merged 23 commits into
mainfrom
brennanb2025/crash-cut-turn-label
Oct 5, 2026
Merged

brennanb2025 merged 23 commits into
mainfrom
brennanb2025/crash-cut-turn-label

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​775 $\color{#cf222e}{\Huge{\mathbf{−}}}$​31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​744
Prod 9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​291 $\color{#cf222e}{\Huge{\mathbf{−}}}$​13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​278

ELI5

Claude or Codex sometimes stops in the middle of a reply without you asking: Orca crashed, you quit Orca, Orca closed an idle chat, or the agent's own process died. When that happened, the cut-off reply said "Failed after 3m 42s" in red. Some of those cuts (a quit, an idle close) had no word in the chat about what happened, only that red label. A crash showed the label and also a notice saying the agent stopped.

Now the reply reads "Worked for 3m 42s" like any other finished reply. Every reply cut off this way shows exactly one notice saying the agent stopped.

What Changed

This is part 1 of 2. Part 2 (#25293) makes the red Failed mark in the sidebar and the other agent-status surfaces clear once you have seen the chat. The two parts land independently, in either order. Until part 2 lands, those surfaces keep marking a cut turn Failed, as they do today.

The problem. When the agent's process goes away mid-reply and nobody pressed Stop, Orca records the turn as ended with no verdict, which it reads as the outcome "interruption". In the chat:

  • The turn bar under your message mapped "interruption" to "Failed after N" (src/shared/native-chat-turn-status.ts).
  • Only some of these cuts wrote a notice row ("Claude stopped while this response was in progress. You can continue in this conversation."):
    • A crash did.
    • A quit, an idle close, a workspace teardown or an orchestration stop wrote nothing.

Before (as you see it): the cut reply reads "Failed after 3m 42s" in red, with a notice row or none, depending on what cut it.

After (as you see it):

  • The turn bar reads "Worked for 3m 42s", the same as a finished turn. This applies on desktop and on the phone.
  • Every turn cut short with nobody asking shows exactly one notice row, with the existing wording, in the same place and style as before. This includes chats from before this change.
  • If you quit Orca and a resume then does not carry the chat on, Orca's note about that resume is the one explanation. That covers a refused resume, one that could not connect, and one that was not confirmed when no new turn opened first. You no longer see "…You can continue in this conversation." next to "Orca couldn't continue this chat after the restart. Send a message to continue it."
  • If the resume does carry the chat on, the cut reply keeps its notice and Orca's "asked this agent to continue" note follows.

What did not change:

  • A real failure still reads "Failed after N".
  • A Stop you pressed, and a turn a newer message replaced, still read "Interrupted after N".
  • An end Orca cannot prove still reads "Couldn't confirm".
  • The sidebar, tab bar, Cmd+J, Activity and the notes send menu are unchanged until part 2.
  • The stored outcome and the wire are unchanged. The host publishes and stores exactly what it did before.

Mechanism:

  • Label: settledTurnStatusKey maps "interruption" to the finished-turn label. The phone renders its turn bar through the same shared function.
  • Notice: the notice is worked out when the transcript is read, not written by the host when it stops the agent.
    • A pure rule, withNativeChatCutTurnNotices (src/shared/native-chat-cut-turn-notice.ts), takes the journal items the transcript reads.
      • It finds root turns whose outcome is "interruption" and that no row already explains.
      • It adds one notice item right after each such turn's last row.
      • The notice uses the existing providerExited wording and error tone, and is scoped to that turn.
    • The desktop chat (use-structured-agent-session.ts) and the phone chat (use-mobile-structured-agent-session.ts) read the journal through this rule. Rows, turn grouping and folding therefore treat the notice exactly like the stored crash row.
    • A row counts as explaining a turn when any of these hold:
      • It states the provider exited (its failure fact). A failed start's row does not count, because it is about a start.
      • It was written by the crash or reopen settlement (its row identity).
      • It is a restart-continuation note saying the chat was not carried on. It is told apart from the "continued" note by its tone, which only those notes carry.
    • The host's writers and this rule share the row identity prefixes (src/shared/agent-session-stop-row-identity.ts). The contract: any new row that explains a stop carries the provider-exited fact or one of those prefixes.
    • A row's tone alone never counts. An agent's own error inside the turn, such as a denied permission, does not explain the stop.
    • A row about the whole conversation explains the cut turn it follows only when nothing in between makes it about something else:
      • A provider-exit row: no message sent since the cut.
      • A row proving the old agent dead on reopen (it is about the cut, never about a send): no other turn.
      • A restart note (it follows the continuation's own message): no other turn.
    • On a journal from a host that does not tag rows with their turn, the notice is placed by order and given no turn tag. This keeps the rest of the transcript grouped the way it was.

Why

Turn bar. Where the common pattern ends a turn because a host or process went away, the turn reads like a finished one ("Worked for N"). Stop wording is kept for a person's own Stop. Saying "Failed" on the turn repeated the notice.

One explanation, derived.

  • Writing the notice when the host stops the agent would leave turns silent:
    • every quit- or eviction-cut turn already in users' journals
    • a quit that dies between ending the turn and writing the row
    • any new stop cause
    • a new client reading an old host
  • With the label now saying "Worked for N", any such turn would look like a success with no word at all.
  • Deriving from the turn record covers all of them, needs no wire change and stores nothing new.
  • Rows the host already writes (after a crash, on reopen) stay the explanation, so nothing is said twice.

Quit and resume.

  • A resume that did not carry the chat on writes a note saying so. That note already says what the notice says: the reply was cut, and you can send a message to continue. Showing both says one failure twice, so the note stands alone.
  • A resume that did carry the chat on writes its note after its own message, so the cut keeps its notice. That state is stable, and nothing flashes away under automatic resume.

Alternatives considered:

  • Write the notice when the host stops the agent, and re-check on reopen. Rejected: journals written before this change would stay silent unless reopen also rewrote history.

Differences from the common pattern

  • Intended: the explanation row is derived when the transcript is read, not stored when the turn is cut. Where the common pattern shows a row, it is stored at the cut.
    • Review found, by reading Orca's stop path, that storing it leaves gaps: turns journaled before this change, and a quit that dies between ending the turn and writing the row, have no stored row. Under the new label those would read as a clean finish with no explanation.
    • Deriving covers them and adds nothing to the wire or to storage.
  • Intended: when a resume after a quit did not carry the chat on, its note replaces the notice for that cut. For "not confirmed", this applies only when no continuation turn opened first.
    • The common pattern hides only a companion row written at the same instant right after the error. Here the note comes later.
    • It states the same failure and the next step, so keeping both would say one failure twice.
  • Intended (known limit): on a host from before 2026-09-29, a turn can show "stopped" twice: once from the host's row and once from the derived notice. This happens when the turn was steered with a mid-turn message and then cut by the provider exiting.
    • Those older hosts do not tag rows with their turn.
    • This is rare and limited to those hosts. Orca does not detect host versions for it.
  • Intended (known limit): chats journaled by older Orca builds recorded some turns the same way as a cut. Those turns now read "Worked for N" with the notice.
    • Before 2026-09-17: a person's Stop, and a failed Codex turn.
    • Between 2026-09-17 and 2026-09-30: a turn replaced by a newer message, and a Stop naming no turn.
    • The stored data cannot tell them apart from a real cut. On main the same turns read "Failed after N", which was also wrong.
  • Temporary: in this part, the sidebar, the chat's tab, Cmd+J recent rows, the review-notes menu and the Activity page keep marking a cut turn Failed in red even after you have seen the chat, as on main. Where the common pattern shows a red mark for this, it lasts only until the chat is read. Follow-up: part 2, fix(sidebar): clear the red Failed mark on a crash-cut turn once the chat is seen #25293.
  • Temporary: a real failure still reads "Failed after N". Follow-up: read real failures the same way ("Worked for N" plus the error row). This is left out to keep this PR narrow.
  • Temporary: the launch-time "Resume interrupted chats?" dialog and the status-bar "N chats to resume" entry are Orca-only and unchanged here. Follow-up: decide whether continuing after a restart should only be the existing "resume automatically" setting. That is a product change and needs sign-off.

Linked Issue

None. Found during live testing of #24233.

Visual Proof

Live check on a second Mac with a dev build carrying both parts and a real Claude chat (isolated profile, app hidden, driven over the DevTools protocol). These are the transcript's screenshots. The sidebar's are in part 2.

Orca killed mid-reply, then relaunched. The reply reads "Worked for 39s" with one notice, "Claude stopped while this response was in progress. You can continue in this conversation.":

Cut reply reads Worked for 39s with one notice

Quit mid-reply, then relaunch with "Resume interrupted chats?".

  • Behind the dialog, the reply reads "Worked for 23s" with one notice.
  • After Resume, the notice stays and Orca's continue note follows. The notice count stayed 1 across 60 samples.
Resume dialog over the cut chat After Resume: notice then continue note

Unchanged cases. A Stop still reads "Interrupted after 53s" with no notice. A real failure (bad model) still reads "Failed after 0s" with its error:

Stop: Interrupted, no notice Real failure stays Failed

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

New tests, one per rule:

  • Derive rule (src/shared/native-chat-cut-turn-notice.test.ts):
    • A cut turn with no row gets one notice after its last row.
    • A turn the crash path already explains gets none, whether the row is scoped to the turn or to the conversation.
    • An exit row counts by its fact. An older host's row counts by its writer.
    • An agent's own permission-denied error row still gets a notice.
    • Stop, a replaced turn, a failure, an unproven end, a finished turn and a running turn get none.
    • A subagent's turn gets none.
    • Several cut turns across history are each handled once.
    • Restart notes:
      • The refused, not-connected and unconfirmed notes count as the explanation when no continuation turn opened first.
      • A continued note (no tone) does not count, so the notice stays before and after it.
      • A restart note after a turn the continuation opened leaves the notice.
    • Conversation-wide rows:
      • A provider-exit row explains no cut turn that a finished turn or a message sent since came after.
      • A row for an old agent proven dead on reopen still explains the cut after a message.
    • A failed start's row does not explain an earlier cut.
    • The notice lands in its turn for desktop and phone grouping.
    • A host that does not tag rows with turns keeps its untagged layout.
    • The older status-row turn shape works, and the notice keeps the same object across reads.
  • Host journals read through the rule:
    • Eviction, quit, and a provider exit seen before a close each show exactly one notice.
    • A person's close shows none.
    • After a crash, only the stored row shows.
    • Before a resume, the quit's notice shows.
    • After a refused resume, only the refusal note shows.
    • After a resume that carries on, the notice stays and the continue note follows.
  • Wiring: the desktop and phone chat hooks each show the notice inside the cut turn.

Ablations. I removed each rule in turn, and its tests went red (16 rules):

  • the derive rule
  • the exit-fact match, the provider-exit and dead-owner writer matches, and the restart-note tone match
  • that a "continued" note doesn't count
  • that a failed start's row doesn't count
  • not matching by tone
  • root turns only
  • "interruption" only
  • the turn reset and the sent-message reset for conversation rows
  • the dead-owner row ignoring a sent message
  • the guard for hosts that don't tag rows
  • the placement after the turn's last row
  • the label mapping

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH and mixed versions: no wire or stored-data change. A new client reading an older host gets the derived notice. An older client reading a new host looks exactly as it does today.
  • A swap while a crash settles: the turn's end can land a moment before the stored exit row. For that moment the reader shows the derived notice, then the stored one in its place, with the same words. There is one row at a time.
  • Folder workspaces: unaffected. The logic keys on the chat journal, not on git.
  • Performance: the rule runs when the journal items change. It returns the same array when no turn needs a notice, so memoized rows are kept.

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)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ne explanation

A reply that an Orca crash or restart cut off said it failed three times: the
turn bar read "Failed after N", the chat's notice row said Claude stopped, and
the sidebar dot stayed red after the chat was read.

The turn bar now reads "Worked for N" for a proven crash/restart cut, the
notice row stays the one explanation, and the sidebar card's dot and agent
rows read failed only until the user has visited the chat (the same
acknowledgement that un-bolds the row), then read done. A failure, a user's
Stop, a replaced turn and an unconfirmed end keep their labels. The stored
outcome is unchanged.
A turn cut off by quitting Orca, an idle eviction or a teardown recorded the
same outcome as a crash-cut turn but wrote no notice row, so with the turn bar
now reading "Worked for N" nothing in the chat said it stopped.

The host stop's settle now writes the existing providerExited notice for the
latest turn when it ends as news (no person's Stop decided it): in the same
write as the host's own turn end, or right after the adapter's. It is keyed by
the turn, and skipped when an error row already explains that turn, so a retry
or an earlier exit row never leaves two.
…riting it at stop time

A turn cut short when the agent stopped without anyone asking now reads
"Worked for N", so it needs a row saying it stopped. Writing that row only on
the quit and eviction paths missed journals written before this change, a quit
that died between its drain and its settle, and any future stop cause.

The transcript now derives it from the journal on desktop and phone alike: a
root turn that ended interrupted with no verdict and no row about the stop
gets one notice in the provider-exit row's words. A stored exit row, matched by
its exit fact or its writer's identity rather than its tone, stays the
explanation, and so does the restart continuation's own outcome note, so a
refused resume says it once. The host's stop path is back to what it was.
…nt on every surface

A turn cut short with nobody asking reads failed only until the user has seen
it. The previous revision passed an optional "seen" flag to each caller, so any
surface that did not pass it, the chat's own tab dot among them, stayed red
beside a sidebar that read Done.

The verdict's display now takes the acknowledgement as part of what it reads:
the entry's stateStartedAt beside the time the user last acknowledged it, both
required, judged by one shared rule. Each surface joins it once where it builds
its rows: the sidebar's agent rows (and so the notes send menu), the workspace
card summary, the terminal tab bar, Cmd-J recent rows, and Activity threads.
Failures, Stops, replaced turns and unproven ends keep their marks as before;
the phone, which has no acknowledgement record, keeps the unseen reading.
…pied

Auto-acknowledgement, the Activity unread count, dashboard row buckets and
notification acknowledgement each spelled the same "acknowledged at or after
the current state began" comparison; they now call the shared rule.
…ies on

A resume after a quit writes its "asked this agent to continue" note after its
own message, so counting that note as the cut's explanation removed the notice
once the resume went on, and made it flash away under automatic resume. Only the
notes that say the chat was not carried on (refused, not connected, not
confirmed) stand in for the notice now.

A row about the whole conversation now explains a cut turn only when no other
turn lies between them, and a failed start's row, which is about a start, never
does. The host writers and the rule share the row identity prefixes, with the
contract that a new row explaining a stop carries the provider-exited fact or
one of them.
…bagent holds the row

A subagent can keep a row working after its main agent was cut, and the row's
clock then predates the cut, so a look at the working chat counted as having
seen the cut and no red mark showed. The mark now compares the acknowledgement
with the later of the row's and the main agent's clocks; auto-acknowledgement of
the chat on screen reads the same clock, and its stamp covers it, so looking at
the chat still clears the mark. Bold rows, dashboard buckets, notifications and
unread counts keep the row's clock.
… no message sent since

A send after a quit's cut whose new agent died before its turn opened leaves a
provider-exit row about the whole conversation. That row is about the send,
not the earlier cut, so the cut kept no explanation. An exit row now explains
a preceding cut only when no message was sent in between; the restart notes,
which follow the continuation's own message, still need only no turn between.

The notes that say a resume did not carry the chat on are now told apart from
the continued note by their tone ('error' or 'warning'), which every host that
wrote them has set, instead of by their words.
… cut after a send

A chat read before the startup reconcile settles its cut turn unverifiable;
if the user then sends a message, the reconcile proves the old agent dead and
writes its row about the conversation after that message. The row names the
old owner's death, never the send, so a message since no longer detaches it
from the cut. Only a provider-exit row, which a later start can write, still
needs no message sent since.
…t map

The card summary now reads acknowledgedAgentsByPaneKey; this test's hand-built
store state lacked it, so every summary read threw.
@brennanb2025

brennanb2025 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Review status: ready for review (part 1 of 2)

This PR was split. Part 1 (this PR) changes how a reply cut off by an Orca crash, quit or idle close reads in the chat. Part 2, #25293, makes the red Failed mark in the sidebar and the other status surfaces clear once you have seen the chat. Each part lands safely on its own, in either order.

The problem. When Orca crashed, quit, or closed an idle chat while Claude or Codex was mid-reply, the reopened chat showed a red "Failed after N" on the reply. A crash added a notice row; a quit or an idle close often left no explanation at all.

What changes for you (this part).

  • The cut-off reply reads "Worked for N" like any finished reply, on desktop and phone.
  • It shows exactly one notice, "Claude stopped while this response was in progress. You can continue in this conversation.", on every kind of cut, including chats saved before this change.
  • The sidebar, tab and other status marks are unchanged here: they stay red, as on main, until part 2 lands.
  • A real failure still reads "Failed after N". A Stop you pressed still reads "Interrupted after N". An end Orca cannot prove still reads "Couldn't confirm".

How it works. The notice is worked out from the saved chat when it is shown, not written when the host stops the agent. Review found, by reading the stop path, that a stored notice would miss chats saved before this change and a quit that dies before writing it. Nothing new is stored or sent between devices.

Fixed during review (this part)

  • Older chats and failed quits had no explanation under the new label.
  • A turn whose only error row was the agent's own (for example a denied permission) wrongly counted as explained.
  • After a quit, a resume that failed showed two rows saying the same thing.
  • A resume that carried the chat on made the notice disappear.
  • Several rare row sequences gave a cut reply no notice, or two.

Deferred (listed in the PR body)

  • Real failures still read "Failed after N".
  • The "Resume interrupted chats?" dialog is unchanged; removing it is a product decision.
  • Some Stops and replaced turns in chats from older builds now show the notice.
  • On hosts older than 2026-09-29, a steered turn that was then cut can show the notice twice.

Seen in live testing, not caused by this PR (needs a decision)

  • When a resume after a quit fails to start, two red rows show.
  • Orca's internal restart instruction shows as a visible user message bubble.

Verified

Not verified: a real person's click at the desktop, the phone in an emulator, SSH hosts, and idle-close or close-it-yourself stops run live.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 3, 2026 19:02

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run read the complete raw diff: all 42 production files plus the surrounding call sites, and re-derived the key behaviors against the repo (not just the new tests).

  • Turn label: settledTurnStatusKey maps interruption to workedFor, so a turn cut by a crash/quit/eviction reads "Worked for N" on desktop and mobile (formatNativeChatTurnStatusLabel shared by both). Stop/replaced stay "Interrupted"; real failures stay "Failed".
  • Derived notice: new pure rule withNativeChatCutTurnNotices (src/shared/native-chat-cut-turn-notice.ts) inserts one provider-exit status row after a cut root turn that no existing row explains; wired into the desktop and mobile structured-session hooks. No storage/wire change; the host writer prefixes were extracted to constants with byte-identical values (verified).
  • Seen-ness: isAgentTurnAcknowledged / agentAttentionStartedAt added, agentVerdictDisplayMark now requires acknowledgedAt, and every surface joins the acknowledgement at row-build time (agent-entry-acknowledgement.ts identity-preserving WeakMap join, card/tab/Cmd+J/Activity/review-notes). latestAgentTurnTimestamp also maxes mainAgent.stateStartedAt so a cut that postdates the row's combined clock (subagent holding the row open) is still cleared on view.

I traced the derive rule's attribution boundaries (turn-scoped vs conversation-wide rows, exit vs owner-death vs restart-note, root-only, interruption-only, old-host no-scope placement), the ack cache-key changes across the card/tab/Activity caches, and the acknowledgement type propagation into every agentVerdictDisplayMark / agentVerdictStatusLine consumer; none produced an actionable defect. The two documented known limits (pre-2026-09-29 untagged-host duplicate, and old journals that cannot distinguish a real cut from an early Stop/replaced turn) are deliberate scope decisions, not regressions.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0d0fb317-5d54-401a-baf8-0b08a64cfee5
📥 Commits

Reviewing files that changed from the base of the PR and between 14e8526 and 62c1a35.

📒 Files selected for processing (3)
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session.ts
  • src/shared/native-chat-cut-turn-notice.test.ts

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


📝 Walkthrough

Walkthrough

Interrupted turns now use worked-time labels, while cancellations remain interrupted and failures remain failed. A shared transcript transformation adds an error notice when an interrupted root turn lacks an existing stop explanation. Mobile and renderer session hooks use the enriched transcript items for message projection. Tests cover notice rules and behavior across session lifecycle cases.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 62c1a

No actionable issue is established for this change; it is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 14e85

The change affects how interrupted replies are displayed and explained. No new privileged operation or access-control bypass was identified, but incomplete baseline coverage limits confidence.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is interruption presentation within desktop and mobile conversations. The inspected transformation consumes existing journal rows and does not introduce a network entrypoint, permission response, credential operation, or journal write.

Trust Boundaries and Controls

  • observed — Synthetic host-worded status rows remain in the read model in the inspected paths. Operational selectors and mutation callbacks retain original session state, so the derived provider-exit fact is not used there to grant permission or dispatch work.

Resilience and Maintainability Implications

  • observed — Inspected restart tests distinguish durable status rows from reader-derived notices. They assert that superseded continuation retains the cut notice without filing a failure, refusal supplies its own explanation, and successful continuation retains the cut notice alongside its continuation note. These are source assertions, not executed runtime results.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.87% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 62 functions across 52 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: cut-off replies from an Orca crash or quit are presented like finished turns, with one explanation. It is specific and related to the changeset, though so…
Description check ✅ Passed The description is detailed and covers the user-facing change, mechanism, rationale, alternatives, visual proof, testing, and compatibility notes. The Linked Issue section says “None” and provides no …
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · 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
brennanb2025 marked this pull request as draft October 4, 2026 20:12
This change now keeps only the cut turn's label and its one derived notice.
The red mark that clears once the user has seen a cut turn moves to its own
change, so each can land alone; until it lands, the sidebar, tab bar, Cmd-J,
Activity and the notes send menu read a cut turn as failed, as before.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — the production change reads correct. Two new tests assert something they do not actually pin, plus one small coverage gap.

Reviewed changes

This run read the complete raw diff (all 20 files) and re-derived the new rule against the host writers that produce the rows it matches.

  • Turn label: settledTurnStatusKey maps interruption to workedFor, so a reply cut by a crash, quit or eviction reads "Worked for N" on desktop and mobile through the shared formatNativeChatTurnStatusLabel. A person's Stop and a replaced turn stay "Interrupted after N"; a real failure stays "Failed after N".
  • One notice, derived on read: new pure rule withNativeChatCutTurnNotices (src/shared/native-chat-cut-turn-notice.ts) inserts one providerExited status row after each cut root turn that no existing row explains, and returns items unchanged otherwise. Wired into the desktop (use-structured-agent-session.ts) and mobile (use-mobile-structured-agent-session.ts) hooks. Nothing stored, no wire change; the host writer prefixes moved to src/shared/agent-session-stop-row-identity.ts with byte-identical string values.
  • Scope reduced since the prior review: the seen-gated red mark moved to #25293, so this diff keeps only the label and the derived notice; the sidebar, tab bar, Cmd+J and Activity still mark a cut turn red-Failed for now.

I traced the rule's matching against settleStructuredAgentSessionDeadGeneration and settleStaleStructuredAgentSessionState (exit row and death row, turn-scoped via exitedRootTurnScope / runningRootTurnScope, else thread-scoped), the unexpected-exit settlement id, and the restart-continuation note (prefix plus error/warning tone). The reducer's creation-sequence rule keeps the revised turn record ahead of the settlement row, so both the scope path and the preceding path populate as intended. No double- or missing-notice case found for the real host shapes.

ℹ️ Nitpicks

  • src/shared/native-chat-cut-turn-notice.test.ts:139 — expect(notice.sequenceIndex).toBeGreaterThan(0) only proves a positive index; a fixed 1 would also pass. Pin the (after.sequenceIndex ?? 0) + 0.5 placement relative to the following row instead.
  • No test exercises a thread-scoped provider-exit row directly after the cut with no message sent since — the cutNoSendSince branch of the 'exit' explanation. The negative (a send since) is covered at line 292; the positive is not.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

]
})
// The list places rows by the same items, so the notice joins the cut turn.
expect(result.current.journalItems.at(-1)).toMatchObject({ turnScope: SCOPE })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last raw item in items (reply-1) already carries turnScope: SCOPE, so this matches whether journalItems is the derived transcriptItems or the raw transportState.journalItems — a regression that reverts only the journalItems: transcriptItems return (hook line 211) would still pass. Pin the notice's identity instead. The mobile test (mobile/src/session/use-mobile-structured-agent-session.cut-turn-notice.test.tsx:130) has the same gap for turnJournal.

Suggested change
expect(result.current.journalItems.at(-1)).toMatchObject({ turnScope: SCOPE })
expect(result.current.journalItems.at(-1)?.itemId).toContain('cut-turn-notice')

…2025/crash-cut-turn-label

# Conflicts:
#	src/main/native-chat/agent-session-wire/structured-agent-session-restart-failure-filing.test.ts
…2025/crash-cut-turn-label

# Conflicts:
#	mobile/src/session/use-mobile-structured-agent-session.ts
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