Repository navigation
Refuse chat resume until terminal writers have exited - #29242
Conversation
…2025/rcc-b-source-admission # Conflicts: # src/main/native-chat/agent-session-journal/journal-legacy-import.ts
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments: 1
src/renderer/src/components/terminal-pane/TerminalPaneResumeInChatButton.tsx: 1
19-26: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffAvoid resolving the full resume source in the reactive selector.
useShallowprevents unnecessary re-renders, but the selector still runs on store updates.resolveCliChatResumeSourceallocates pane state and performs host and launch-route checks. Keep the reactive selector cheap, or resolve the full source from current state in the click handler, asCliChatResumeMenuItemsdoes.
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
1372160a-221d-42fa-bfe6-61ffebaa35dc
📒 Files selected for processing: 160
config/scripts/vitest-node-runtime-files.mjssrc/main/agent-hooks/agent-hook-runtime-deps.tssrc/main/agent-hooks/server-agent-presence.test.tssrc/main/agent-hooks/server-opencode-writer-observation.test.tssrc/main/agent-hooks/server/cli-writer-observation.tssrc/main/agent-hooks/server/server-agent-presence.tssrc/main/agent-hooks/server/server-authority-aliases.tssrc/main/agent-hooks/server/server-ingest-normalization.tssrc/main/agent-hooks/server/server-ingest-terminal.tssrc/main/agent-hooks/server/server-listeners.tssrc/main/agent-hooks/server/server-status-application.tssrc/main/agent-hooks/server/server-status-update.tssrc/main/agent-hooks/server/server-tmux-status.tssrc/main/ai-vault/session-scanner-discovery-wsl-gate.test.tssrc/main/ai-vault/session-scanner-discovery.tssrc/main/claude/claude-cli-conversations.tssrc/main/codex/codex-ai-vault-session-resume.tssrc/main/codex/codex-cli-conversations.tssrc/main/daemon/pty-subprocess-spawn-file-foreground.test.tssrc/main/daemon/pty-subprocess/spawn-file-child-processes.tssrc/main/daemon/pty-subprocess/subprocess-handle.tssrc/main/daemon/session-subprocess-handle.tssrc/main/daemon/session.tssrc/main/daemon/terminal-host-non-agent-foreground.test.tssrc/main/daemon/terminal-host-process-inspection.test.tssrc/main/daemon/terminal-host-process-inspection.tssrc/main/daemon/terminal-host.tssrc/main/grok/grok-transcript-import.tssrc/main/native-chat/agent-session-journal/adopted-transcript-source.tssrc/main/native-chat/agent-session-journal/journal-legacy-import.tssrc/main/native-chat/agent-session-journal/journal-newest-item-window.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adopted-import.tssrc/main/native-chat/agent-session-wire/structured-agent-session-attach.tssrc/main/native-chat/agent-session-wire/structured-agent-session-create-adoption.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-create.tssrc/main/native-chat/codex-adoption-preparation.tssrc/main/native-chat/session-file-resolver.tssrc/main/native-chat/structured-agent-cli-conversation-adoption.test.tssrc/main/native-chat/structured-agent-cli-conversations.tssrc/main/native-chat/structured-agent-cli-source.tssrc/main/native-chat/structured-agent-session-history-adoption.test.tssrc/main/native-chat/structured-agent-session-history-adoption.tssrc/main/native-chat/structured-agent-session-source-admission.test.tssrc/main/native-chat/transcript-opencode-sqlite-query.test.tssrc/main/native-chat/transcript-opencode-sqlite-query.tssrc/main/native-chat/transcript-opencode.test.tssrc/main/native-chat/transcript-opencode.tssrc/main/native-chat/transcript-opencode2-sqlite.test.tssrc/main/native-chat/transcript-opencode2-sqlite.tssrc/main/native-chat/transcript-path-admission.tssrc/main/native-chat/transcript-reader.tssrc/main/omp/omp-session-cwd-lookup.tssrc/main/omp/omp-transcript-import.tssrc/main/opencode/opencode-transcript-import.tssrc/main/orcad/orcad-entry.tssrc/main/pi/pi-transcript-import.tssrc/main/providers/local-pty-child-process-verdict.test.tssrc/main/providers/local-pty-foreground-inspection.tssrc/main/providers/local-pty-provider-foreground-process.test.tssrc/main/providers/local-pty-provider.tssrc/main/runtime/agent-session-founding-admission.tssrc/main/runtime/agent-session-reservation-admission.tssrc/main/runtime/agent-status-observed-pane-identity.tssrc/main/runtime/construction/runtime-construction-dependencies.tssrc/main/runtime/construction/runtime-construction.tssrc/main/runtime/orca-runtime-structured-agent-session-create-intent.test.tssrc/main/runtime/rpc/core.tssrc/main/runtime/rpc/dispatcher-stream-options.tssrc/main/runtime/rpc/methods/orchestration/worker/fleet-status-observed-identity.test.tssrc/main/runtime/rpc/methods/structured-agent-session-adoption-replay.test.tssrc/main/runtime/rpc/methods/structured-agent-session-create-handler.tssrc/main/runtime/rpc/methods/structured-agent-session-create.tssrc/main/runtime/rpc/methods/structured-agent-session-precommit-refusal.test.tssrc/main/runtime/rpc/methods/structured-agent-session-precommit-refusal.tssrc/main/runtime/rpc/methods/structured-agent-session.test.tssrc/main/runtime/runtime-terminal-state-records.tssrc/main/runtime/session-tabs/structured-session-account-home.tssrc/main/runtime/session-tabs/structured-session-cli-writer-host.tssrc/main/runtime/session-tabs/structured-session-create-support.tssrc/main/runtime/structured-agent-cli-writers.test.tssrc/main/runtime/structured-agent-cli-writers.tssrc/main/runtime/structured-agent-session-create-adoption.tssrc/main/startup/main-process-runtime-service.tssrc/renderer/src/app-shell/use-app-startup-hydration.tssrc/renderer/src/components/native-chat/structured-agent-session-status-bridge-unconfirmed-start.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionSurfaceSwitchMenuItems.tsxsrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.tssrc/renderer/src/components/tab-bar/CliChatResumeMenuItems.tsxsrc/renderer/src/components/tab-bar/SortableTab.tsxsrc/renderer/src/components/tab-bar/SortableTabContextMenu.tsxsrc/renderer/src/components/tab-bar/TabSessionSurfaceSwitchMenuItems.test.tsxsrc/renderer/src/components/tab-bar/TabSessionSurfaceSwitchMenuItems.tsxsrc/renderer/src/components/tab-bar/cli-chat-resume-feedback.tssrc/renderer/src/components/tab-bar/cli-chat-resume-run.test.tssrc/renderer/src/components/tab-bar/cli-chat-resume-run.tssrc/renderer/src/components/tab-bar/cli-chat-resume-source.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.test.tssrc/renderer/src/components/tab-bar/tab-session-history-switch.tssrc/renderer/src/components/tab-bar/use-sortable-tab-context-menu.test.tssrc/renderer/src/components/tab-bar/use-sortable-tab-context-menu.tssrc/renderer/src/components/terminal-pane/TerminalPaneResumeInChatButton.test.tsxsrc/renderer/src/components/terminal-pane/TerminalPaneResumeInChatButton.tsxsrc/renderer/src/hooks/ipc-events/session-tab-ipc-bridge.tssrc/renderer/src/i18n/en-runtime-required.jsonsrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/lib/agent-session-launch-plan.tssrc/renderer/src/lib/ai-vault-tab-title-requests.tssrc/renderer/src/lib/cli-conversation-exit-guidance.tssrc/renderer/src/lib/launch-structured-agent-session.tssrc/renderer/src/lib/structured-agent-launch-settlement.tssrc/renderer/src/lib/structured-agent-session-launch-admission.test.tssrc/renderer/src/lib/structured-agent-session-launch-callers.tssrc/renderer/src/lib/structured-agent-session-launch-cancellation.test.tssrc/renderer/src/lib/structured-agent-session-launch-cancellation.tssrc/renderer/src/lib/structured-agent-session-launch-create-call.tssrc/renderer/src/lib/structured-agent-session-launch-errors.tssrc/renderer/src/lib/structured-agent-session-launch-exited-owner.test.tssrc/renderer/src/lib/structured-agent-session-launch-failure.tssrc/renderer/src/lib/structured-agent-session-launch-outcome-tracking.tssrc/renderer/src/lib/structured-agent-session-launch-persistence.test.tssrc/renderer/src/lib/structured-agent-session-launch-persistence.tssrc/renderer/src/lib/structured-agent-session-launch-record.tssrc/renderer/src/lib/structured-agent-session-launch-registry.tssrc/renderer/src/lib/structured-agent-session-launch-reload.tssrc/renderer/src/lib/structured-agent-session-launch-resume-identity.test.tssrc/renderer/src/lib/structured-agent-session-launch-surface.tssrc/renderer/src/lib/structured-agent-session-launch.tssrc/renderer/src/lib/structured-agent-session-provisional-tab.tssrc/renderer/src/lib/workspace-session-hydration-keys.tssrc/renderer/src/lib/workspace-session-unified-tabs.tssrc/renderer/src/runtime/paired-host-structured-launch-bookkeeping.test.tssrc/renderer/src/runtime/structured-agent-session-client.tssrc/renderer/src/runtime/structured-agent-session-tab-retirement.test.tssrc/renderer/src/runtime/structured-agent-session-tab-retirement.tssrc/renderer/src/runtime/web-session-client-owned-tab-placement.tssrc/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.tssrc/renderer/src/store/slices/tab-group-state.tssrc/renderer/src/store/slices/tabs-hydration.tssrc/renderer/src/store/slices/tabs/structured-chat-close-owner.test.tssrc/renderer/src/store/slices/tabs/tabs-close-actions.tssrc/renderer/src/store/slices/tabs/tabs-create-actions.tssrc/renderer/src/store/slices/tabs/tabs-drop-actions.tssrc/renderer/src/store/slices/tabs/tabs-group-actions.tssrc/renderer/src/store/slices/tabs/tabs-label-actions.tssrc/renderer/src/store/slices/tabs/tabs-move-actions.tssrc/renderer/src/store/slices/tabs/tabs-reconciliation.tssrc/renderer/src/store/slices/tabs/tabs-slice-contract.tssrc/renderer/src/store/slices/worktrees/teardown/worktree-purge-state.tssrc/shared/agent-session-definitive-refusal.tssrc/shared/agent-session-refusal-details.tssrc/shared/agent-session-refusal-notice.test.tssrc/shared/agent-session-refusal-reason-words.tssrc/shared/protocol-version.tssrc/shared/rpc-contract/structured-agent-session-params.tssrc/shared/structured-agent-session-create.test.tssrc/shared/structured-agent-session-create.tssrc/shared/structured-agent-session-mutation.tssrc/shared/structured-agent-session-surface-capabilities.tssrc/shared/workspace-layout/tab-order.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
…check - The POSIX shell-alone proof skips a session leader below the shell and everything under it: a prompt's zpty worker has left the terminal, while suspended, background, nohup and disowned jobs still refuse. - Windows steady-state polls read the cached process table; every read that decides adoption or close stays fresh. - The pane header and tab menu point back to Session History, since their action is gone once the agent exits.
…-stop-before-adopt # Conflicts: # src/main/ai-vault/session-scanner-discovery.ts # src/main/claude/claude-cli-conversations.ts # src/main/codex/codex-cli-conversations.ts # src/main/grok/grok-transcript-import.ts # src/main/native-chat/structured-agent-cli-conversations.ts # src/main/native-chat/structured-agent-cli-source.ts # src/main/native-chat/structured-agent-session-source-admission.test.ts # src/main/native-chat/transcript-opencode-sqlite-query.ts # src/main/omp/omp-transcript-import.ts # src/main/opencode/opencode-transcript-import.ts # src/main/pi/pi-transcript-import.ts # src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts # src/main/runtime/rpc/methods/structured-agent-session-adoption-replay.test.ts # src/main/runtime/session-tabs/structured-session-create-support.ts # src/main/runtime/structured-agent-session-create-adoption.ts # src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.ts # src/renderer/src/components/tab-bar/cli-chat-resume-feedback.ts # src/renderer/src/components/tab-bar/cli-chat-resume-run.test.ts # src/renderer/src/components/tab-bar/cli-chat-resume-run.ts # src/renderer/src/i18n/en-runtime-required.json # src/renderer/src/lib/launch-cancel-tombstones-compat/structured-agent-session-launch-cancellation.test.ts # src/renderer/src/lib/launch-cancel-tombstones-compat/structured-agent-session-launch-cancellation.ts # src/renderer/src/lib/structured-agent-session-launch-outcome-tracking.ts # src/renderer/src/lib/structured-agent-session-launch-surface.ts # src/renderer/src/runtime/paired-host-structured-launch-bookkeeping.test.ts # src/renderer/src/runtime/structured-agent-session-tab-retirement.test.ts # src/renderer/src/runtime/structured-agent-session-tab-retirement.ts # src/renderer/src/store/slices/tabs/tabs-slice-contract.ts # src/renderer/src/store/slices/worktrees/teardown/worktree-purge-state.ts
A pane's job keeps every process it ever started, including daemons whose parent exited (an rc-started ssh-agent), so such panes never read as an idle shell and the chat switch refused forever. Walk descendants of the shell along live parent links within the job, for every shell; the Git Bash launcher keeps its bash-chain allowance and a child older than its parent still refuses.
… the shell" MSYS exec and fork leave intermediate Win32 parents exited, so a live Claude started through a script (and 'sleep 60 &') became unreachable and the chat switch admitted while it ran. Back to the job census until a console-based design is measured.
…cision A pane's job keeps every process ever started in it, so an rc-started ssh-agent made a Git Bash pane read as busy forever and the chat switch refused. MSYS fork/exec leaves live work under exited Win32 parents, so parent links alone can't tell the daemon apart; it is the one that also left the console. On a scanChildProcesses decision read (switch admission, run-terminal close, close guards), a member neither linked to the shell nor attached to its console no longer counts. Polls and shell confirmation keep the job census. The console-list helper now forks through the process-host chokepoint.
Keeps the daemon router and terminal host within their line budget now that the options carry scanChildProcesses.
…r close Closing a Windows pane kills its whole job, so a close check that ignored a detached process (gitk &, cmd /c start ...) would kill it without asking. Adoption asks whether anything is still in the terminal; close asks whether closing destroys something. Only the adoption read now sends ignoreDetached; close guards and the run-terminal sweep keep the job census. The win32 test also stops the ssh-agent it starts.
…ning its chat When the conversation already had a native chat, the switch hit the ownership conflict first and the header opened that chat, even while the terminal's agent (a later claude --resume) was still running. The switched-out terminal is now checked before the conflict, so a live one refuses as usual and one at rest still opens the owning chat.
A plain read can answer unverifiable while the eval's short-lived processes exit; either way it must not read as no-children.
… for any switch Session History's Resume in New Native Chat carries no source pane, so with an earlier chat still holding the conversation it skipped the terminal check and the owning chat's first send would start a second writer beside a live terminal Claude. Every adoption request now runs the writer check before the owner conflict, as the no-owner path already does.
…-stop-before-adopt
…-stop-before-adopt # Conflicts: # src/main/startup/main-process-runtime-service.ts
Review loop status: ready for Brennan's review (head 59c72b3)The problem"Resume in New Native Chat" (pane header, tab menu, Session History) could open a native chat on a conversation that a Claude CLI in a terminal was still writing. The user saw two writers on one conversation: the most visible case was a Claude paused with Ctrl+Z. The shell prompt was back, so Orca took the terminal as idle and adopted the conversation, and What changes for the user
What the loop fixed (8 review rounds plus bounded reviews; design panels on the repeated cases)
Deferred or labelled (the full list is in the PR body; the new refusal copy is English-only for now, and other locales fall back to English as before)
Verified
For Brennan's reviewmacOS, base (ccdfcd6) vs branch: Pure prompt, macOS, base vs branch: Claude exited and macOS on 4f956b5: adopt after exit; header refusal while Claude is live (no tab opened); header refusal for an already-adopted conversation resumed in the terminal. Windows: Git Bash with an rc Three places where a human judgment matters:
|
…-stop-before-adopt # Conflicts: # src/main/agent-hooks/server/server-ingest-terminal.ts # src/main/agent-hooks/server/server-listeners.ts # src/main/runtime/rpc/methods/structured-agent-session-adoption-replay.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat-launch.ts # src/renderer/src/lib/structured-agent-session-launch-failure.ts









ELI5
Before this change, "Resume in New Native Chat" could take over a conversation while the terminal agent that started it was still running there, for example paused with Ctrl+Z or running in the background. Resuming that agent later meant two programs writing the same conversation. Now Orca refuses while the agent may still be in the terminal it ran in, and adopts the conversation with its full history once the agent has exited.
What Changed
What the user sees:
&orbg,nohup, or disowned) is refused. Nothing is stopped or closed.~/.bashrcstartsssh-agentno longer blocks the switch after the agent exits. Closing that pane still asks for confirmation, as on main, because closing really does endssh-agent.How it works:
nohupand disowned jobs are counted. On Windows the check uses the terminal's process job, with the Git for Windows bash launcher chain treated as the shell; for adoption only, a job member that is both off the shell's live parent chain and detached from the pane's console (an rc-startedssh-agent) is not counted.ssh-agentis both, so it no longer blocks. Jobs started with&,nohup, Ctrl+Z, and agents launched from a script stay attached to the console, so they still count. Only the adoption read asks for this, through one new optional request field,ignoreDetached. Closing a pane, tab or window, and the cleanup of finished run terminals, keep the full job census and its confirm dialog, because closing a Windows pane ends its whole job.nohup,disown).Why
Chat ownership alone could not see a terminal writer, so two writers on one conversation were possible. The common pattern keeps a registry of the executions that host a conversation, alive until the execution ends, and asks it before attaching a new writer. Orca's execution unit here is the terminal, because Orca does not own a process the user started, so the registry lives on the terminal's process record and dies with it. Refusing with a clear next step avoids building stop operations that can outlive their recovery surface. Counting jobs in the terminal's session, rather than every process the shell ever started, keeps prompt helpers from blocking a switch without a fragile list of helper names. On Windows, adoption and close ask different questions: adoption asks "could something here still write this conversation?", while close asks "would closing end something the user cares about?". That is why only adoption sets detached daemons aside.
Linked Issue
Part of STA-10705. The base PR #29150 has merged; this PR now targets main.
Visual Proof
Real Electron QA screenshots are in the review status comment. macOS QA passed on the current head 4f956b5; Windows QA passed on 7caa594 (one test-only commit and the owned-conversation commit before the current head; that commit is platform-neutral).
Testing
gitchild (admits) beside a suspended or background job (refuses); an OpenCode ownership proof that survives an unmarked copy; an uninspectable terminal; a tmux-published event; and a new terminal process dropping the old links.ssh-agentis set aside for the adoption read, never reads as idle for close, and a latersleep 60 &still counts for adoption.sleeprefuses, and the conversation is adopted once it exits; for a conversation an earlier chat adopted, the header's "Resume in New Native Chat" refuses while Claude runs in the terminal and the chat opens after Claude exits.ssh-agentpane is adopted after the agent exits, with the console helper running from the terminal daemon; a live Claude launched from a script refuses from History and from the header;sleep &andnohup sleep &refuse; closing still asks for confirmation; reopening a conversation an earlier chat holds from the pane header refuses while Claude runs in that terminal. Since 7caa594 only platform-neutral code in the chat-create path changed (the owned-conversation check). Ctrl+Z and a barenohup claude &couldn't be tested on Windows, because native Windows Claude doesn't suspend and exits by itself under a barenohup; the macOS run covers both.sfrompsstat, verified on macOS. The light Linuxpscheck was blocked (no zsh on the shared Linux box).Review
Six review rounds, then three bounded reviews of the later fixes (the adoption-only Windows rule, the header reopen check, and the owned-conversation check on the current head). Every finding is fixed or labelled below. The earlier automatic-stop design is removed from this PR; #29367 is parked.
Agent skill upstream boundary
Notes
Reliability class: agent-session.provider-ownership. The adoption check runs only when the user acts and adds no saved data. It adds one optional request field to the terminal daemon,
ignoreDetached, sent only by the Windows adoption read. Older Orca clients never send it. An older or relocated terminal daemon ignores it and keeps the full job census until it restarts, so thessh-agentover-refusal stays with that daemon until then (temporary, see below). On Windows, status polls of a Git Bash terminal now read the process table to judge the terminal's process job (see the temporary list).SSH and remote: remote and WSL terminals refuse while open, and loss of contact is never treated as exit. No conversation links are recorded for SSH terminals; status rows and saved records that belong to an SSH connection are checked by their own host, and an older host keeps its existing behaviour. SSH terminals, including on Windows hosts, refuse while open.
Intended differences
nohupand disowned jobs still refuse. The common pattern judges only the terminal's foreground or ignores helpers by name; Orca also refuses background jobs because it prevents two writers, and it uses no name list.coprocfrom rc files) refuses until it exits, because it can't be told apart from a job.zpty) isn't seen by the terminal check, because the check sets aside only a session leader below the shell, together with its subtree. Claude is still caught by its process check while the terminal the conversation ran in is still open. Wrappers such asscript,expect,sudowithuse_ptyandsetsid -wstay in the terminal's session, so they are counted and refuse. Launches that detach to run on their own (setsidwithout-w, detached tmux) were never in the terminal's process tree; they fall under the escaped-agent limit in the temporary list.setsid, or launched withcmd /c start claude, is detached from the console and has lost its parent chain, so the terminal check doesn't see it. Windows hooks report no agent process to probe, so the escaped-agent check can't catch it either.ssh-agent, or that launched a detached window (for examplegitk &orcmd /c start), still asks before closing, as on main.Temporary differences
nohup,disown) works only while the terminal the conversation ran in is still open; once that terminal closes, Orca treats its work as finished. It covers Claude on macOS and Linux only, because today only Claude's hooks report its process. Codex in a detached tmux is still admitted, as before. Extending process capture belongs to the agent-presence work.ssh-agentpane on it still refuses until the terminal closes.claude --resumeafter an app restart (local macOS, hibernation off) #14228 (by code trace; base not rerun): after a cold restart, Orca can relaunch a Claude that had already exited.Checklist