Skip to content

fix(native-chat): settle a reply cut off by a server restart - #26460

Merged
brennanb2025 merged 16 commits into
mainfrom
brennanb2025/ssh-chat-orphaned-turn-settles
Oct 10, 2026
Merged

brennanb2025 merged 16 commits into
mainfrom
brennanb2025/ssh-chat-orphaned-turn-settles

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1264 $\color{#cf222e}{\Huge{\mathbf{−}}}$​58 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1206
Prod 35 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​398 $\color{#cf222e}{\Huge{\mathbf{−}}}$​116 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​282

ELI5

When the server running your chats (for example the Orca server on an SSH host) restarted in the middle of a reply, the cut-off reply came back with no status at all: no time worked, and the sidebar said "Couldn't confirm". It never cleared, not even after the next message.

Now that reply is settled. It shows how long the agent worked (up to its last saved output, so the server's downtime never counts), a muted line saying Orca on that host stopped while the response was in progress, and Continue. The sidebar no longer says "Couldn't confirm". Chats that an earlier version already left stuck this way heal the next time the server starts and the chat is opened.

Temporary: until #25670 lands, the turn bar reads "Worked for N" and the sidebar and tab show the same red Failed that a reply cut off by quitting the desktop app shows today. Follow-up: #25670, after which they read Interrupted.

What Changed

Before: after the SSH server restarted mid-reply, the cut-off reply showed no status. This happened whenever Orca could not prove that the old agent process was gone (for example a process it could not inspect, or one that would not exit when signalled). The turn was left unconfirmed (the sidebar said "Couldn't confirm") and nothing ever settled it.

After: the cut-off reply is settled as interrupted. On screen today that is "Worked for N", the muted line "Orca on stopped while this response was in progress.", and Continue; a follow-up message works as usual. The time worked ends at the agent's last saved output (or its last heartbeat, if that is later), never at the restart time, so the server's downtime never counts as work. Chats an earlier start already left showing "Couldn't confirm" are healed the same way on the next start.

Copy change: that muted line used to say "Orca on was closed while this response was in progress." For a remote host that reads as if someone closed Orca, which is often false: a server Orca runs on an SSH host, or a headless server, was stopped, not closed by a person. A chat on a remote host now says "stopped" for this case, which is true whether that host is a managed server, a headless server, or another desktop shared through "Share this host" whose user quit it. When the desktop app itself is quit mid-reply, its line still says "was closed". This is a desktop-only wording change (the stored row and what the server sends are unchanged); it is translated in every shipped language. The phone app shows the server's own generic words for this row and is unaffected.

How: a new server opens the chat records only while it holds the host's single-instance lock, so every earlier server that wrote them has exited, and every agent those servers ran lost its connection to the chat with them. When the new server loads the records, it notes (in memory, never saved) which ownership generations belonged to those earlier servers. When a chat's history is next opened, any turn they left running, or that an earlier start could only mark unconfirmed, is ended as interrupted, with the stop line attached.

Unchanged:

  • how leases are judged and released
  • the process verdicts (live / unverifiable / exited)
  • the cleanup that stops a surviving agent process
  • the rule that only the execution host's own evidence counts; a client losing contact proves nothing
  • no new stored field, remote call, or wire change. The new stop line reuses the existing stale-session: notice id prefix, so older desktops render it.

Earlier in this PR: buffered agent output is timed by when it was saved, and the host's own recovery writes don't count as agent output. Both feed the "time worked" above.

Why

On macOS and Linux, an agent process can outlive its server for a few seconds while its supervisor stops it, but its output pipe goes to the dead server, so nothing it does reaches the chat. Requiring proof that each agent process is gone left the chat silent whenever the host couldn't produce that proof. The common pattern settles a replaced server instance's in-flight work as interrupted, keyed on the server instance's identity rather than per-process proof, and so does this change.

  • Intended: the settle runs when the chat's history is next opened, instead of as a sweep over every chat at startup. Orca opens histories lazily, and each open derives the same answer from what the new server loaded, so nothing is owed in between.
  • Intended: the end time is the agent's last saved output or heartbeat, not the recovery time, so downtime never counts as work.
  • Intended: the process verdict stays "unverifiable" when nothing proved the process gone; only the turn is settled.
  • Intended: the "stopped" wording is chosen on the desktop from where the chat runs (a remote host), not sent by the server. A remote host records every graceful stop the same way, whether a managed server was stopped, a headless server was stopped, or a desktop shared as a host was quit by its user, so the desktop cannot tell them apart. "Stopped" is true in all three cases (an earlier draft said "restarted", which is false for a shared desktop its user quit). Telling them apart would need a new server-sent cause, a wire change for no real gain.
  • Temporary: the time worked is a lower bound in this case. A graceful stop of the remote server (for example being signalled to stop) does not settle its chats itself; the next server infers the end from the last saved output, so live time with no output after it is not counted. When the desktop app is quit, the end is the quit instant instead. Follow-up: run the chat shutdown from the server's own graceful stop, so a server stop ends turns at the stop instant like a desktop quit.
  • Temporary: on Windows there is no agent-process supervisor, so after Orca crashes the old agent may keep running (and editing files) for a while after the chat shows the reply as cut off, and Continue starts a second agent beside it. Nothing that old agent does reaches the chat. Allowing a new send after an unproven release is not new; settling the turn with Continue is. Follow-up: stop Windows agent processes when Orca dies (for example a job object that kills them when Orca closes).

Intended: known narrow case. If an earlier start released the chat's owner without proof and then failed before it settled the turn, the owner's last heartbeat is overwritten by that release. If the turn also saved no further output, it ends at its own start ("Worked for 0s"); a turn Orca opened itself (for example a command turn) instead settles with no end time and shows no duration, because Orca never invents an end time. The time shown is a lower bound; the record holds nothing better, and this PR deliberately adds no stored field for it. It is safe to keep: it needs a failed start in between, it only under-counts time worked, and the common path (no release in between) keeps the heartbeat.

The doubled partial reply seen in an earlier QA run came from the QA stand-in agent, which replays its whole saved transcript, including assistant rows, on every resume. It is not changed here.

Linked Issue

Internal structured-chat restart repair (U30); no existing issue linked.

Visual Proof

Live run on a managed SSH host (Linux container host, desktop on macOS, a credential-free stand-in Claude), with this PR's changes combined with the other SSH chat fixes. A long reply was streaming when the host's Orca server got a stop signal; Orca restarted the server. The cut-off reply shows how long the agent worked, the partial text (through chunk 28 of 60), the muted line "Orca on QA SSH run4 stopped while this response was in progress.", and Continue. A follow-up message then completed normally.

A reply cut off by a server restart, settled with time worked, the stopped line and Continue

Automated tests also drive the production host with real SQLite chat history and check the result through the same projection the chat UI renders: one interrupted turn, one stop line with Continue, and the time worked.

Testing

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

On macOS, with env -u ORCA_USER_DATA_PATH and --maxWorkers=2, on the previous head: 179 tests passed across 20 explicitly listed files (the 13 test files this PR adds or changes, plus the 7 acquisition tests that open a journal without the store). An independent review run on an earlier head also passed 572 tests across 60 restart-related files. For the copy change, on this head: 62 tests passed across the 3 renderer test files that render the stop line or build what it is told (NativeChatNoticeRow, NativeChatInterruptedContinue, native-chat-transcript-slots).

What the new and changed tests cover:

  • A reply cut off by a killed server, reopened by a new server: one interrupted turn ending at the last saved output, one stop line, and after reattach and a new send the turns read interrupted then completed with nothing duplicated.
  • A turn an earlier start left unconfirmed ("Couldn't confirm" in the sidebar) heals on the next start, and a third start changes nothing.
  • The crash-turn-end cases now settle on the first open (no brief "Couldn't confirm" flash), publish exactly one interruption to the status feed, and a failed settle leaves the turn running so the next open derives the same end.
  • Only an acquisition's open may skip the record store; a reader open without it no longer compiles (a type-level test), so the stuck "Couldn't confirm" behaviour can't come back silently.
  • The stop line for a remote host's graceful stop says "stopped"; the desktop's own quit still says "was closed"; a remote host's update and crash keep their words; a chat tells its rows whether its host is remote.
  • An independent review reverted the production changes with these tests in place: 10 tests across 4 files failed, and all pass on the PR head.

Node typecheck passed on an earlier head (no main-process code has changed since); the web typecheck passes on this head. orca-ci-checks --no-typecheck on this head: all 22 local static checks pass (lint, code quality, localization, file size).

Not verified: WSL, native Windows and Linux runs, live mixed-version peers, and how long a Windows agent keeps working after Orca dies. No Claude structured-session files change, so the real-Claude run is not required.

Review

Reviewed in rounds; the latest found no blocking issues. It confirmed the host lock and single record store mean every earlier writer has stopped, that no surviving agent can write to the chat, that this server's own turns can never be caught (its fences are always above the loaded ones), and that terminal agents and other hosts' chats are excluded. This PR stays a draft until the live SSH screenshots are attached.

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 same host chat history serves local desktop, SSH, and WSL execution, and folder workspaces as well as git worktrees; the settle does not read workspace kind. Remote chat records name their own host as local, so the fix applies on the managed SSH server. The verdict comes only from the execution host's own lock and server identity, never from a client losing contact. Older rows keep their existing timestamps; no missing save time is invented.

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)

…ot flushes

Shared-timeline checkpoints stamped the coalescer flush or retry time as the
provider's latest proof of life, so a turn whose provider died before a delayed
flush settled with an inflated duration after a host restart. Carry each delta's
receipt time through the coalescer and timeline lane.
A reply cut off by a server restart could show "Worked for 0s" because recovery
ended the turn at the last heartbeat or the reply's first timestamp. End it at
the latest time this owner's output was saved to chat history instead, derived
in the existing journal fold from the row save time. Drops the per-frame
receipt plumbing through every provider and buffer.
Mark journal-open and connection-cleanup revisions with the existing recovery
annotation so an interrupted turn does not include host downtime. Preserve
buffered provider output and advance restart fixtures to the restart clock.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary

Problem: when the SSH server restarts mid-reply (a crash, an update, or a manual restart), the reply is cut off. After the restart, the cut-off reply could show "Worked for 0s". Recovery ended the turn at the last heartbeat or at the reply's first timestamp, both of which can be well before the last output.

User-facing change: a reply cut off by a restart now shows roughly how long it actually ran. Main already marks it Interrupted; that part is unchanged.

How: the turn now ends at the last time the agent's output was saved to chat history. That time comes from the save time the chat history already records, read in the same pass that rebuilds the chat on open, so nothing new is stored. It is bounded by when the old server process was confirmed gone; losing contact alone never counts as that confirmation. Saves made by the server itself during recovery, such as marking a dead chat's background tasks unknown or closing out a lost connection, don't count as output.

Review rounds:

  • An earlier version recorded a receipt time on every output event, carried through every agent and buffer. Reviews kept finding buffers that dropped it, so it was replaced by the save-time design above. The production change went from 31 files to about 10.
  • Round 4 found that the server's own cleanup after a restart counted as output, so downtime was counted as work. Fixed by marking those writes as recovery, and every other write the server makes after a restart was checked for the same mistake.
  • Round 5 (independent, head 5ca43e4): no findings. 139 tests pass, and the regressions fail before the fix.

Accepted trade-off: output that sat in a buffer before it was saved counts until it was saved, so under heavy load the time can read slightly long. It never shows 0s for a reply that streamed.

Not verified: a real agent dying with a real SSH server, a rendered desktop, Linux, Windows or the phone. The tests use the real chat history and server code with scripted agents. Live SSH QA across all the SSH chat fixes is still to come, and screenshots will go in the PR body before this is marked ready.

CI: the one red check, "static analysis", is a line-limit lint error that was on main when this ran (fixed on main by #26648). It will clear on the final sync with main.

When the managed server died mid-reply and a new one started on the same
host, the cut-off turn settled `unverifiable` with no end whenever the old
provider's identity could not be proven gone, and nothing ever revised it:
the chat showed no turn status and the sidebar said "unconfirmed".

The new server opens the chat records holding the host's instance lock, so
every runtime that wrote them before is gone, and every provider it held
lost its pipes with it. Record that at load (the fences those runtimes
granted, the loaded owner's last renewal, how its runtime ended) and let
the stale settle end those runtimes' running or unverifiable turns as
interrupted at the owner's last saved output or renewal, with the existing
Orca-stop row. Lease adjudication and the live / unverifiable / exited
owner verdicts are unchanged.
…settles

Only an acquisition's open may skip it; a reader open without the store would leave a replaced server's turns unverifiable.
@brennanb2025 brennanb2025 changed the title fix: preserve streamed turn duration after host restart fix(native-chat): show a reply cut off by a server restart as Interrupted Oct 9, 2026
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary (final review, head 876915c, plus round-7 small fixes at 414c424)

Problem: on the SSH server, if the server process was killed or restarted while Claude was replying, the restarted server never settled that reply. The chat showed the cut-off reply with no status, and the sidebar showed it as unconfirmed, forever. The server waited for proof that the old agent process had died, and after a restart that proof is unavailable.

User-facing change: a reply cut off by a server restart now shows as Interrupted, with a duration that ends at the agent's last saved output, so server downtime isn't counted as work. You can continue the chat normally. Chats already stuck this way are fixed the next time the server starts.

How: the server only runs while it holds its single-instance lock. When a new server instance loads the chat records, any reply owned by an earlier instance on the same host is from a server that is gone. A surviving agent can't write to the chat, because agents talk to the server over pipes that die with it. Those replies settle as Interrupted when the chat is opened. Nothing new is stored: the list of replaced instances is rebuilt in memory on every start. This matches the common pattern of settling in-flight work when the instance running it is replaced.

Review rounds: six earlier rounds covered the duration-only version of this fix. Live SSH QA (Q8) then showed the reply stayed unconfirmed after a real restart, so round 6 changed the evidence to "the instance was replaced".

  • Final review (independent, head 876915c): no P0–P2 findings.
    • It checked the instance-lock guarantee, that a surviving agent can't reach the chat, and that this server never settles its own turns.
    • It confirmed the fix applies on the SSH server, using the real Q8 chat log.
    • Removing the fix makes 10 tests in 4 files fail; on the head, 572 tests in 60 files pass. The edited older tests were not weakened.
    • The doubled reply seen in Q8 came from the QA stand-in agent replaying stored replies, not from Orca.
  • Round-7 small fixes (414c424):
    • The record store is now required by the type system for every open except an acquisition, so a future caller can't silently bring back "stuck unconfirmed". A type-level test covers it.
    • The PR body now labels the Windows gap as temporary. Windows has no agent supervisor, so after a crash the old agent can keep running while the chat shows Interrupted. Follow-up: stop Windows agent processes when Orca dies.
    • A narrow case where the duration shows 0s is documented rather than fixed, because fixing it would need new stored state.
    • 179 targeted tests, the full node typecheck and the local CI checks (22/22) pass.

Not verified: a live SSH re-run of Q8 on this head (running next on the combined test build; screenshots go in the PR body before ready), WSL, native Windows or Linux hosts, a real Claude login, the rendered UI, mixed old/new peers.

… closed

A reply cut off when a remote host's Orca server stopped read "Orca on <host> was closed while
this response was in progress." Nobody closed anything there: the server stopped and Orca started
it again before the row could show. For a chat on a remote environment, cause quit now reads
"Orca on <host> restarted while this response was in progress." The desktop's own quit keeps
"was closed". Renderer-only; no wire change.
@brennanb2025 brennanb2025 changed the title fix(native-chat): show a reply cut off by a server restart as Interrupted fix(native-chat): settle a reply cut off by a server restart Oct 9, 2026
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary addendum (rounds 8–9, head cf91fbc)

Why another round: live SSH QA killed the SSH host's Orca server mid-reply. The fix worked: the reply was settled with the time worked, Continue appeared, and a follow-up worked. But the reply's notice read "Orca on was closed while this response was in progress." That wording comes from a desktop quit, and here nobody closed anything. The PR body also overstated what users see.

Changes:

  • Wording: for a chat on a remote host, the notice now reads "Orca on stopped while this response was in progress." It is true for a managed server, a headless server, and a shared desktop host whose user quit it. Round 8's "restarted" was false for that last case, so round 9 replaced it.
    • Quitting your own desktop still says "was closed".
    • The update and crash wordings are unchanged.
    • Only the desktop renders the new wording; nothing sent between desktop and server changed. The phone already shows the server's own generic text.
    • All six locales are updated.
  • PR body and title: now describe what users actually see:

Independent review of round 8 (head a6fd5c6): no P0–P2.

  • The remote/local wording keys on the chat's own execution host.
  • Only the desktop transcript rewords the row.
  • The new tests fail with the change reverted (2 of 36) and pass with it.
  • The core mechanism is unchanged.

Its P3s are fixed in round 9: the "restarted" wording, the old sidebar label quoted wrongly in the body, a stale comment, and unlabelled differences. The coordinator checked the round-9 diff: wording, locales, comment and test only.

Checks on cf91fbc: 62 targeted renderer tests pass, the web typecheck passes, and the local CI checks pass 22/22. Hosted CI is running.

Still to do before ready: a live SSH run showing the new wording, with screenshots in the PR body. At the final sync with main, resolve small conflicts with main's #26544 in the notice-row files, keeping both changes.

…-orphaned-turn-settles

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
#	src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
#	src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
…-orphaned-turn-settles

# Conflicts:
#	src/main/native-chat/agent-session-journal/journal-row-writer.ts
#	src/main/native-chat/agent-session-journal/journal-store-collaborators.ts
#	src/main/native-chat/agent-session-journal/journal-store.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-dead-generation-settlement.ts
…utput rig

Main's #26502 made Grok declare compaction; the rest-test rig adapter has no compact, so the
registry refused it before any recovery case ran.
…-orphaned-turn-settles

# Conflicts:
#	config/scripts/vitest-sqlite-runtime-files.mjs
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary addendum (main syncs, head 8bffceb)

Why more work after round 9: main merged #26664, "settle a dead agent's leftover work in one write that always fits". It rewrote the same code this PR changes, the code that settles replies a dead or replaced server left unfinished.

What the merge did:

  • fix(native-chat): settle a dead agent's leftover work in one write that always fits #26664's single write is now the only way leftover work gets settled.
  • This PR's replaced-server pieces go through that same write rather than a second path:
    • the interrupted verdict;
    • the revision of replies an earlier start left "couldn't confirm";
    • the "Orca on stopped…" line.
  • The end time still comes from the agent's last saved output, so server downtime isn't counted as time worked.
  • One narrow case changed, following main's rule never to invent an end time: a turn Orca opened itself (a command turn) that saved no output now settles with no end time and shows no duration. The PR body says so.

Independent review of the merge (head 9c6a3ca): no P0–P2.

  • There is exactly one settlement write, and nothing is settled twice.
  • The stop line's position matches main's, and fix(native-chat): settle a dead agent's leftover work in one write that always fits #26664's size guarantee is untouched.
  • Nothing was dropped from either side.
  • Every caller of the journal commit passes the time it actually saved.
  • Tests: 655 pass, plus 1,149 more in journal, ACP and Codex tests. Turning the replaced-server input off fails exactly the expected 10 tests.

Its two P3s are fixed: a stale code comment (9997d44) and the PR body's wording for the narrow case.

Live proof: the managed-SSH run above (Visual Proof) shows the cut-off reply settled, with the time worked, the "stopped" line and Continue.

Latest sync (8bffceb): one trivial conflict, in a list of tests that run under Node, with both sides kept.

  • Local checks: node and web typechecks, the PR's 170 tests, and the local CI checks (22/22).
  • CI: everything passes except cross-version wire compatibility (and verify, which only collects that result). Seven tests about Kiro session-search downgrades fail there.
  • That failure isn't from this PR. The same failure appears on unrelated open PRs built on current main (runs 38002452149 and 38002439754), and this PR touches none of those files.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 9, 2026 23:26
@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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: c382b580-22db-4cbb-98cc-e4516a246feb

📥 Commits

Reviewing files that changed from the base of the PR and between 452e444 and 8bffceb.


📒 Files selected for processing (50)
  • config/scripts/vitest-sqlite-runtime-files.mjs
  • src/main/codex/codex-background-terminals.ts
  • src/main/native-chat/agent-session-journal/journal-open.ts
  • src/main/native-chat/agent-session-journal/journal-provider-activity.ts
  • src/main/native-chat/agent-session-journal/journal-reducer.ts
  • src/main/native-chat/agent-session-journal/journal-row-table.ts
  • src/main/native-chat/agent-session-journal/journal-row-writer.test.ts
  • src/main/native-chat/agent-session-journal/journal-row-writer.ts
  • src/main/native-chat/agent-session-journal/journal-store-collaborators.ts
  • src/main/native-chat/agent-session-journal/journal-store-restore.ts
  • src/main/native-chat/agent-session-journal/journal-store.ts
  • src/main/native-chat/agent-session-journal/journal-subagent-liveness.test.ts
  • src/main/native-chat/agent-session-timeline/provider-timeline-decision.ts
  • src/main/native-chat/agent-session-timeline/provider-timeline-transition-layout.ts
  • src/main/native-chat/agent-session-timeline/provider-timeline-turn-decisions.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-conversation-open.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-crash-turn-end.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-dead-generation-settlement.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-readable-restorer.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-replaced-server-turn.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-restart-provider-output.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-restart-recovery-output.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-restart-restore.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-restart-stream-duration.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-stale-stop-row.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-stale-turn-verdict.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-stale-turn-verdict.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-transition.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-unexpected-exit.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-wedged-profile-migration.test.ts
  • src/main/runtime/agent-session-record-store.ts
  • src/main/runtime/agent-session-replaced-runtime.test.ts
  • src/main/runtime/agent-session-replaced-runtime.ts
  • src/main/runtime/agent-session-store-transactions.ts
  • src/renderer/src/components/editor/editor-cmd-save-target.ts
  • src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
  • src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
  • src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
  • src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
  • src/renderer/src/components/native-chat/native-chat-orca-stop-context.ts
  • src/renderer/src/components/native-chat/native-chat-orca-stop-words.ts
  • src/renderer/src/components/tab-bar/tab-bar-item-surface.tsx
  • src/renderer/src/components/terminal-workspace-model.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json

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



📝 Walkthrough

Walkthrough

Journal writes now retain save timestamps and track provider activity by owner fence. Session recovery uses saved activity and replaced-runtime evidence to settle interrupted turns and create stop notices. The change adds restart and replacement tests, plus remote-host status and localized wording for Orca stop notices. Several renderer expressions and the background-terminal call were reformatted without changing their behavior.


Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 8bffc

The reviewed restart behavior is consistent with marking a reply interrupted when its former host can no longer write to the chat. No merge-blocking issue was established.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 36.21% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 44 files. (6 skipped:… 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: settling replies interrupted by a server restart. It is concise and specific.
Description check Passed The description is detailed and covers the user impact, implementation, rationale, testing, limitations, visual proof, and checklist. The Linked Issue section states that no existing issue is linked, …
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.

Full details: Docstring Coverage

Explanation

Docstring coverage is 36.21% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 44 files. (6 skipped: 6 unsupported.)



  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🛠️ Fix failing CI checks 💡
  • 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 added a commit that referenced this pull request Oct 15, 2026
* fix: preserve streamed turn duration after host restart

* test: run restart duration regression in Node

* fix: preserve provider receipt time in journal updates

* test: verify provider receipt in Claude ownership assertion

* fix(native-chat): time interrupted turns from real provider output, not flushes

Shared-timeline checkpoints stamped the coalescer flush or retry time as the
provider's latest proof of life, so a turn whose provider died before a delayed
flush settled with an inflated duration after a host restart. Carry each delta's
receipt time through the coalescer and timeline lane.

* fix(native-chat): derive interrupted-turn duration from saved output

A reply cut off by a server restart could show "Worked for 0s" because recovery
ended the turn at the last heartbeat or the reply's first timestamp. End it at
the latest time this owner's output was saved to chat history instead, derived
in the existing journal fold from the row save time. Drops the per-frame
receipt plumbing through every provider and buffer.

* fix(chat): exclude host recovery revisions from provider activity

Mark journal-open and connection-cleanup revisions with the existing recovery
annotation so an interrupted turn does not include host downtime. Preserve
buffered provider output and advance restart fixtures to the restart clock.

* fix(native-chat): interrupt turns a replaced server left running

When the managed server died mid-reply and a new one started on the same
host, the cut-off turn settled `unverifiable` with no end whenever the old
provider's identity could not be proven gone, and nothing ever revised it:
the chat showed no turn status and the sidebar said "unconfirmed".

The new server opens the chat records holding the host's instance lock, so
every runtime that wrote them before is gone, and every provider it held
lost its pipes with it. Record that at load (the fences those runtimes
granted, the loaded owner's last renewal, how its runtime ended) and let
the stale settle end those runtimes' running or unverifiable turns as
interrupted at the owner's last saved output or renewal, with the existing
Orca-stop row. Lease adjudication and the live / unverifiable / exited
owner verdicts are unchanged.

* fix(native-chat): require the record store for any journal open that settles

Only an acquisition's open may skip it; a reader open without the store would leave a replaced server's turns unverifiable.

* fix(native-chat): say a remote host's Orca restarted, not that it was closed

A reply cut off when a remote host's Orca server stopped read "Orca on <host> was closed while
this response was in progress." Nobody closed anything there: the server stopped and Orca started
it again before the row could show. For a chat on a remote environment, cause quit now reads
"Orca on <host> restarted while this response was in progress." The desktop's own quit keeps
"was closed". Renderer-only; no wire change.

* fix(native-chat): say a remote host's Orca stopped, true for a shared desktop too

* test(native-chat): register Grok without compaction in the recovery-output rig

Main's #26502 made Grok declare compaction; the rest-test rig adapter has no compact, so the
registry refused it before any recovery case ran.

* chore(native-chat): say a replaced runtime also revises an unverifiable turn

(cherry picked from commit b44a579)
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