Repository navigation
fix(native-chat): keep an idle chat alive while its subagents or background commands run - #22794
Conversation
12c5548 to
b815e8c
Compare
b815e8c to
8bbedb6
Compare
Review statusReady to merge after #22802. No code changes were needed. Head is still Cause and fix. The 30-minute idle check only counted a running lead turn, or a message waiting for the agent to start. Subagents, background commands and monitors keep running inside the same agent process after the lead's turn ends, so the check stopped the process and that work died silently. The PR makes the check read the same live background-task list the sidebar uses to show the chat as working. This fixes the cause directly instead of adding a second guard. Nothing new is stored. Can a stuck entry keep a chat alive forever? Checked for both Claude and Codex. Their lists only hold tasks the provider still reports as running. Failed, stopped and completed tasks move to the settled list, which this check does not read. So a failed or out-of-contact task cannot keep the process alive. Each new agent process starts with an empty list, and the list is cleared when the process exits. If the provider never reports a task as finished, the sidebar already shows the chat as working. The user can stop the task or close the tab, and quitting still stops every agent. Expected behaviour to be aware of. A background command that never ends, such as a dev server started in the background, now keeps an unviewed chat's agent process running until the command is stopped, the tab is closed, or the app quits. That is the intended outcome: stopping it silently was the bug. As the PR body notes, a chat is released up to 30 minutes after its last background task finishes. Validation
Minor, not blocking
Still open: live Electron QA screenshots, which are coming from a separate QA lane. |
8bbedb6 to
55a9106
Compare
Live Electron QA (whole stack)Isolated dev instance (hidden window, driven over CDP), macOS, real Claude Code sessions. The idle window was shortened to 45 s in the QA build only. A "before" build swaps one fix's files back to
The scenario that crashes Claude mid-chat was not driven live; the #22802 integration tests cover it. Windows, Linux and SSH were not run live: the changes are host-side and not platform-specific, and no wire format changed. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Idle release gate now honors live child work —
hasOwedWorkinstructured-agent-session-host-lifetime.ts:204gained a third arm readingcontext.deps.adapter.backgroundTaskState?.(sessionId)?.tasksthroughagentChildWorkLiveness, so a settled lead with running subagents, background commands or monitors re-arms the release clock instead of being evicted. - Release-clock comment —
structured-agent-session-release-clock.ts:10-14now names the third owed-work arm beside the running turn and the held send. - Coverage — the test file moved to
structured-agent-session-owed-work-release.test.tsand adds anit.eachoverlocal_agent/local_bash/monitordriving the real Claude adapter over a fake connection: the session survives several idle windows while a task is live, then releases once the task settles and its wake-up turn completes.
Validation I ran: the file's 7 tests pass; removing only the new hasOwedWork arm fails exactly the 3 new cases at structured-agent-session-owed-work-release.test.ts:262, so the coverage is load-bearing. The agentChildWorkLiveness input is the same raw backgroundTaskState().tasks list the sidebar's structuredAgentSessionAgentStatus folds (the status feed only strips totalTokens, which is liveness-neutral), so there is no divergence between the two answers.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…ground commands run The idle release clock stopped a chat's provider process 30 minutes after the last viewer left unless the lead agent's own turn was still running. A lead whose turn had finished but whose subagents, background commands or monitors were still running counted as idle, so stopping the process ended that work silently. Owed work now also counts the provider's live background roster, read through the same liveness rule the sidebar uses to show a chat as working.
55a9106 to
fecd6f5
Compare
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe host lifetime check now treats live adapter background tasks as owed work. Tests cover released sessions with running subagents, background commands, and monitors. They verify that the session remains open while the task runs and while its resulting lead turn is unsettled, then closes after that turn settles. Comments describe the same release-clock behavior. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to Released chats can remain available while background work runs and close after that work and its resulting turn settle. No actionable merge-blocking risk is established. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Released chats will no longer discard running background work. A task that never finishes, however, can also keep an unused agent process running without a time limit. No externally reachable exploit was established. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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. Comment |

ELI5
When you leave a native chat, Orca waits 30 idle minutes and then stops the agent process behind it to free memory. It only checked whether the main agent was mid-answer. If the main agent had finished but its subagents, background commands or monitors were still running, Orca stopped the process anyway and that work died without a word. Now that work counts as busy, and the chat is only stopped once it has finished.
What Changed
hasOwedWorkinstructured-agent-session-host-lifetime.ts) now also reads the provider's live background roster throughagentChildWorkLiveness, the same rule that makes the sidebar show the chat as working. The two existing conditions (a running lead turn, or a message held while the provider is still starting) are unchanged.Why
The host already knew about this work: the sidebar status and the restart-resume snapshot both fold the live background roster into "working". The idle check was the one place that asked a narrower question, so it could stop a chat that everything else was showing as busy. Reusing the same liveness rule keeps those answers the same.
The roster is in-memory state rebuilt from the provider's own task events and cleared when the process goes away, so nothing durable can latch a chat alive. When the tasks settle, the next check releases the chat normally.
Alternatives considered:
Known difference: when the clock finds work still running, it re-arms a full 30-minute window. So a chat can be stopped up to 30 minutes after its last background task finishes, not right away.
Linked Issue
No separate issue. This was deferred out of #22560 as its own decision: "A chat whose main agent finished and that nobody is viewing is still stopped after 30 idle minutes ... which ends any subagent or background command it left running."
Visual Proof
Live Electron QA on an isolated dev instance (hidden window, CDP), macOS, real Claude Code sessions. The idle window was shortened to 45 s in the QA build only. "Before" builds swap in one file set from
mainat a time and restart the app. Process liveness comes frompson the provider pid the app spawned.Prompt: run
sleep 150; echo finishedin the background and end the turn, then switch to another worktree.sleepkilled. On return the task reads "Background task stopped · Background shell command didn't finish before the previous session ended".sleepfinished by +150 s, Claude woke and replied, and the provider was stopped by +180 s. On return the task readsdone · 2m 30s, "completed (exit code 0)", and a new send is answered.Testing
structured-agent-session-owed-work-release.test.ts(renamed fromstructured-agent-session-starting-release.test.ts, which already covered the other owed-work case). New cases use the real Claude adapter over a fake connection: the lead's turn settles while a subagent, a background command or a monitor keeps running. The chat is released, the test waits out several idle windows and checks the chat is still alive, then the task settles and the chat is stopped. A finished background command wakes the lead for a new turn, and the test settles that turn too.Ablation: with the production change removed, all three new cases fail; with it, all 7 in the file pass.
Neighbouring suites: surface lifetime, send preparation, and the hold RPC (66 tests) pass.
pnpm tc:node, oxlint, andcheck:code-quality:changedare clean.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)