Skip to content

fix: stop Claude and agent helper processes when Orca quits or crashes (STA-9254, 1 of 2) - #25715

Merged
brennanb2025 merged 24 commits into
mainfrom
brennanb2025/supervised-one-shot-cli
Oct 6, 2026
Merged

brennanb2025 merged 24 commits into
mainfrom
brennanb2025/supervised-one-shot-cli

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1223 $\color{#cf222e}{\Huge{\mathbf{−}}}$​46 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1177
Prod 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​314 $\color{#cf222e}{\Huge{\mathbf{−}}}$​54 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​260

ELI5

Orca sometimes runs an agent's command-line tool (the CLI) briefly in the background: to ask Claude for its model list, to list another agent's models, or to write a commit message, pull request text or a branch name. If Orca quit or crashed while one of those was running, nothing stopped it. A CLI that hung kept running after Orca was gone.

Now each of those runs under a supervisor: a small watcher process that stops the CLI when Orca goes away. That covers a normal quit, a crash, and Orca being force-killed alike.

What Changed

Before:

  • To list Claude's models (for example when the model picker asks), Orca runs claude -p … briefly. Model discovery for other agents and commit-message / PR / branch-name generation run an agent CLI the same way.
  • Each was a plain child of Orca. Its 60 s timeout only ran while Orca was alive.
  • If Orca quit, crashed or was killed mid-run, the CLI was left behind, reparented to the system. A CLI that hung (a stalled network call, a keychain prompt) kept running indefinitely. On test machines with a stand-in claude that ignores end of input, every launch left one process behind.
  • A timeout or cancel killed only the CLI's top process, so helpers it had started could survive even that.

After (macOS and Linux):

  • These CLIs are stopped when Orca quits or crashes, together with anything they started.
  • A timeout or cancel stops the CLI's whole process group. It asks first (SIGTERM) and forces it (SIGKILL) only if it doesn't stop.
  • Error messages are unchanged. A missing CLI still reads "… not found on PATH", and a CLI that can't be started still reads "… could not be started" or "… failed to start", as before.
  • Windows and WSL are unchanged (see the differences below).

The plan, and which step this is:

  • Step 1, this PR:
    • the supervisor's one-shot mode;
    • every CLI run through the source-control agent launcher: the Claude model-list probe, model discovery, and commit-message / PR / branch-name generation;
    • stopping those CLIs through main's shared close (Share the managed process lifecycle for structured providers #25204);
    • when Orca dies, each provider is closed the way Orca itself closes it.
  • Step 2, next PR, stacked on this one:
    • short-lived Codex app-server sessions (Codex model list, hook trust, session-index heals);
    • Codex backfill recovery;
    • the OpenCode launch preflight. That one leaks even with the real CLI, because it starts a server.

Mechanism:

  • The supervisor (src/main/provider-process/provider-process-supervisor.ts). Native chat already runs Claude and Codex under it. It starts the CLI in its own process group, checks every 100 ms that Orca is still its parent, and stops the whole group when Orca is gone. "Stopping the group" means SIGTERM, a grace period, then SIGKILL, and the supervisor exits only once the group is gone.
  • A new "one-shot" lifetime. A one-shot CLI reads its request until its input ends. In a session, end of input means "Orca is closing", so the supervisor used to stop the CLI about 1 s into its answer. In one-shot mode, end of input passes straight through to the CLI.
  • Built on main's shared close (Share the managed process lifecycle for structured providers #25204). Main already closes every structured provider through one sequence, closeProviderProcess, driven by each provider's close policy, and spawns them through spawnManagedProviderProcess. This PR reuses that and adds no second stop sequence. What it adds:
    • The source-control launcher (spawnSourceControlAgent) runs every POSIX CLI under the supervisor in one-shot mode. Its stop (stopSupervisedChildProcess) is main's closeProviderProcess with the root-only policy: ask the supervisor (SIGTERM), wait its full stop time, and only then force the process tree. Forcing it sooner would kill the supervisor and orphan the CLI's group. After a forced teardown, main's 1 s forced-exit grace for the CLI's own exit now applies to these one-shots too.
    • When Orca dies, the supervisor makes the provider's own close request (closeRequest in its spec). spawnManagedProviderProcess derives it from the provider's close policy, so a provider's own close and its owner-death close come from one source:
      • Claude's policy signals the supervisor on close (end of input plus SIGTERM), so a gone Orca stops Claude at once.
      • The Codex app-server's policy is the root-only default (end of input, then its grace), so it can finish writing its state. Any provider on the root-only default drains the same way on owner death.
      • One-shots are stopped at once.
  • Supervisor fixes found along the way. These also reach native chat:
    • The CLI's argument list is passed as real arguments. Before, it went inside one environment variable, which Linux caps at 128 KiB, so a large prompt passed as an argument failed.
    • The supervisor waits, bounded, for the CLI's last output to reach Orca before exiting. Before, it could drop the tail of a large answer read by a busy Orca.
    • A CLI that can't be started is reported on one marked line, which Orca maps back to the message a direct start gave. Native chat's Details also shows the plain spawn <cmd> ENOENT text, not the marker.
    • The user's NODE_OPTIONS no longer reach the supervisor itself; they are handed to the CLI. Before, a bad value could stop even a non-Node CLI such as Codex. The CLI's environment is still resolved once, by the shared resolveProviderChildEnv; the supervisor only keeps these two Node startup keys out of its own.
    • For one-shots only: once the CLI has exited under a stop, the rest of its group is killed at once, instead of waiting out the 3 s grace. Sessions (native chat) keep the full grace, so an MCP server or a tool's child still in the group can finish cleaning up.
    • A forced stop is filed in crash breadcrumbs under the source-control site, through the shared teardown's caller label, not as a Codex teardown.

Why

The failure is a missing owner: nothing stops the CLI when Orca goes away, however it goes away. The supervisor already gives native chat exactly that guarantee in production, so this reuses it rather than adding a second mechanism.

Alternatives considered:

  • A kill list run at quit. It covers a graceful quit only. A crash, a force-kill or a killed process group skips it, and it would be a second mechanism beside the supervisor.
  • A long-lived host process per provider that runs the one-shots and dies with Orca. It gives the same crash guarantee but is a large new subsystem. The supervisor we already run per child gives it for every one-shot.
  • A kill tied to Orca's own lifetime (a finalizer, or a timer that stops the child). It stops a one-shot on a timeout or a graceful exit, but not on a crash. That is the gap here.
  • Recording children and reaping them at the next launch. Orphans keep running until then, and it adds persisted state for something that can be derived.

Differences from the common pattern:

  • Intended:
    • One supervisor per child, not one long-lived host process per provider. The common pattern stops a one-shot on a timeout, a cancel or a graceful owner exit, but gives no general guarantee when the owner crashes. This PR does, at the cost of one small process per one-shot (below).
    • No ledger. State is derived, and nothing is persisted.
    • When Orca dies, each provider is closed the way Orca closes it. Codex drains on end of input; Claude is stopped at once.
    • Native chat is touched too, through the shared supervisor: spawn-failure Details text restored to the plain spawn error, and NODE_OPTIONS kept out of the supervisor. Its stops are otherwise unchanged: a session's group keeps the full SIGTERM grace after the CLI exits. The Codex connection and Claude suites stay green.
  • Temporary:
    • Windows and WSL get no crash coverage yet: STA-9579, a per-child kill-on-close job. That also closes native chat's existing Windows gap.
    • Still unsupervised: the Codex rate-limit probe (STA-9576), --version and status one-shots (STA-9577), and the Claude account and login commands (STA-9578).
    • SSH: the relay's exit while a remote exec is in flight is not covered (STA-9581). The remote probe itself is deliberately unchanged: it belongs to the relay on the execution host, and losing contact is not proof the process died.

Cost:

  • About 16-17 MB of physical memory per one-shot, for its lifetime.
  • About 75-150 ms of added latency per one-shot.
  • Nothing multiplies at startup: model discovery is deduplicated, so there is about one probe per launch and one per user action.

Linked Issue

Part of STA-9254 (Linear): step 1 of 2. Step 2 is the stacked PR for the Codex app-server sessions, Codex backfill recovery and the OpenCode launch preflight.

Visual Proof

Nothing on screen changes; the change is which processes are still running after Orca exits. These are the recorded process checks from the live QA run (macOS, isolated rig, a stand-in claude that ignores end of input and starts a sleep grandchild, the Claude model-list probe in flight). Liveness was read per pid with start-time checks to rule out pid reuse.

Before (main 17ecee6): the hung probe and its grandchild outlive Orca.

main: 20.8 s after Orca quit, the stand-in claude and its sleep grandchild are still alive, reparented to pid 1
After a normal quit, both are still alive 20.8 s later, reparented to pid 1.

main: after Orca is SIGKILLed, the stand-in claude and its grandchild are still alive when the 20 s watch ends
After Orca is killed, both are still alive when the 20 s watch ends.

After (this PR): everything is gone in under half a second.

this PR: 0.48 s after Orca quit, the stand-in claude, its grandchild, the supervisor and Orca are all gone
After a normal quit, the stand-in, its grandchild and the supervisor are gone 0.48 s after the quit.

this PR: 0.37-0.38 s after Orca is SIGKILLed, the stand-in claude, its grandchild and the supervisor are all gone
After Orca is killed, the stand-in, its grandchild and the supervisor are gone 0.37-0.38 s after the kill.

Testing

Live QA (macOS, on ed5201b). A rig with an isolated profile and a stand-in claude that ignores end of input. Process ids were recorded and checked by pid. The later commits don't change one-shot quit or crash behaviour.

Build Quit Crash
main 17ecee6 stand-in and grandchild alive 20.8 s after quit alive after 20 s
PR1 gone in 0.48 s gone in 0.37-0.38 s

Automated (macOS).

  • Real-process supervisor tests:
    • a one-shot answers after its input ends;
    • a CLI that ignores end of input is reaped when its owner is SIGKILLed or quits cleanly;
    • SIGTERM-ignoring helpers are SIGKILLed;
    • the full output reaches a slow reader;
    • a near-cap argument reaches the CLI intact;
    • NODE_OPTIONS reach only the CLI;
    • start failures (missing, not executable, not a program on macOS, a path through a file, no pid) come back as the right message;
    • when the owner dies, a Codex-style session drains and a Claude-style session is stopped at once.
  • Launch, stop and composition tests: generation and discovery with the Codex home lock under cancel, timeout and output limit, through a real supervisor or a fake that exits on SIGTERM; and the Claude model-list probe through a real supervisor with a stand-in CLI.
  • The suites that drive fake children now run the Windows path, taskkill included.
  • Each fix was ablated: undoing it fails its tests. That includes deriving the owner-death close request from the close policy, forced either way: Claude's and Codex's spawn-spec tests catch it.
  • Checks run:
    • 98 test files on the merge with main's Share the managed process lifecycle for structured providers #25204, including main's shared-close tests (1025 passed, 6 Windows-only skipped);
    • the node and CLI typechecks;
    • oxlint over the whole repo;
    • the changed-code quality gate;
    • the max-lines ratchet;
    • the reliability-gate manifest check;
    • oxfmt.

Not verified:

  • The Linux runtime. The E2BIG fix is covered only by a unit test of the argument and environment sizes, because macOS has no per-string limit.

  • The real Claude, Codex and OpenCode CLIs. Every test used stand-ins.

  • Windows CI is pending.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

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

  • Cross-platform: macOS and Linux are supervised. Windows and WSL keep today's direct spawn and their tree kill (STA-9579).
  • SSH: remote probes are unchanged (see above).
  • Folder workspaces: unaffected. The probe runs with no working directory, and generation passes its working directory through unchanged.
  • Mixed versions: nothing persisted and no wire change.

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)

…d relay a provider's last output

A one-shot CLI (claude -p, codex exec -) reads its request until stdin ends, so the
supervisor's session rule (stdin end means the owner is closing) would stop it about
1 s into its answer. The one-shot lifetime passes stdin end through and leaves only
the owner-death watch and explicit signals to stop it.

The supervisor also exited as soon as its provider did, dropping output still in the
pipes when the owner reads slowly. It now waits, bounded, for the provider's output
to be relayed before exiting.
…or on POSIX

The Claude model-list probe, model discovery for every agent, and commit message,
pull request and branch name generation all spawned the agent CLI as a plain child
of Orca. If Orca quit, crashed or was killed while one was running, nothing stopped
it, and a CLI that hung kept running after Orca was gone.

They now run under the provider supervisor that native chat already uses, in its
one-shot lifetime, so Orca's exit stops the agent's whole process group however
Orca exits. A timeout or cancel asks the supervisor to stop (SIGTERM) and only
tears the tree down once it has had its full stop time; killing the supervisor
first would orphan the agent's group. A missing binary now reaches Orca as the
supervisor's exit 127, which is mapped back to the existing not-found message.
Windows and WSL keep spawning the agent directly.
…p every spawn error back, and share one stop ladder

- The supervisor read the provider's command and arguments from one base64 JSON env string.
  Linux caps a single env string at 128 KiB, so an argv prompt of about 90-120 KiB, which
  passes the 120 KiB per-argument guard, failed execve with E2BIG. The provider argv now
  follows the supervisor script's '--' as real arguments; the env keeps only small fields.
- A supervisor that cannot start its provider reports Node's spawn error line and exits 127.
  That line now becomes the same error a direct spawn emits, so ENOENT still reads as
  'not found on PATH' and EACCES or any other spawn error reads as 'failed to start'.
- stopSupervisedProvider is the one ask, wait and force ladder: it gives a supervisor its full
  stop time before forcing. Agent one-shots, the Codex app-server close and the Claude child
  exit proof now share it, with the same requests, bounds and forced steps as before.
…derr line

Node throws most spawn failures (ENOEXEC, ENOTDIR, ELOOP, EPERM, ...) instead of emitting
them, and the supervisor had no catch, so it died with exit 1 and a stack trace that the
user saw as the agent's failure. A thrown or emitted spawn failure now exits 127 with one
marked line carrying whether it was thrown, its code and its message. Orca reads only the
last stderr line, so a runtime warning printed earlier cannot hide it, and maps it to the
message a direct spawn gave: thrown is 'could not be started', ENOENT is 'not found on
PATH', and any other emitted error is 'failed to start'.
…r and hand them to the provider

The supervisor runs Electron in Node mode, which honours NODE_OPTIONS and
NODE_REPL_EXTERNAL_MODULE. A user value such as a --require of a missing file stopped
the supervisor from starting, breaking even native CLIs like Codex that never load it.
The launch now takes both out of the supervisor's environment, carries them in its spec,
and restores them for the provider only, so a Node-based CLI still gets them.
… own breadcrumb site

The forced tree teardown recorded every self-initiated kill as the Codex app-server's, so
a forced commit-message or model-discovery stop read as a Codex teardown in crash
breadcrumbs. The teardown now takes the caller's site; source-control stops pass the
site their Windows tree kill already uses.
…tput limit; name the direct-child suites

The commit-message suites that drive fake children spawn them directly, the unsupervised
shape Windows and WSL use, so they now say so. The supervised POSIX stop gets its own
compositions: a timed-out Codex generation settles at once but holds the Codex home until
its supervisor has stopped (faithful fake, fake timers), and an agent that floods past the
output limit is stopped through its real supervisor with no process left behind.
…h and cover supervised discovery

The commit-message suites that drive fake children mocked the supervisor away on POSIX, so
they asserted a direct root SIGKILL that production no longer takes there. They now pin the
platform to Windows (with an empty PATH, so host installs cannot answer a bare agent name)
and assert the Windows kill, taskkill included. The three tests that check the host's own
discovery spawn shape run on the host and read the agent argv past the supervisor's '--'.
Model discovery gets its supervised composition: a timed-out Codex discovery settles at once
but holds the Codex home until its supervisor has stopped.
…e spawn error it was

Native chat's exit errors carry the provider's stderr tail into Details. Under the supervisor
a missing CLI left the supervisor's internal spawn-failure report there instead of Node's own
'spawn <cmd> ENOENT'. The report, its parser and a display formatter now live in one module;
the Codex app-server and Claude stream-json exit errors pass the tail through the formatter,
which turns a report back into the spawn error and leaves any other stderr unchanged.
…crashing on its missing pipes

When the provider spawn fails outright (EMFILE, ENFILE), Node emits 'error' later and leaves
the child with no pid and no stdio. Piping stdin into the missing pipe threw first, so the
supervisor died with exit 1 and a stack trace and never wrote its spawn-failure report. The
pipes are now wired only for a provider that started.
Agent one-shots stop their supervisor with SIGTERM through the shared stop ladder, watching
the child's own exit. That adapter moves into one helper beside the ladder, so other
supervised children can use it with their own stop request instead of a copy. The caller's
breadcrumb site still reaches the forced teardown. Same request, wait and force as before.
… its owner dies

The owner-death watch went straight to the group SIGTERM and cancelled any stdin-end grace,
so when Orca quit or crashed a session provider such as the Codex app-server never saw the
EOF that lets it finish writing its state (auth.json, the state database). A session whose
owner is gone now closes as an owner's stdin end does: the provider's stdin is ended, it
gets the stdin-end grace, and only then the SIGTERM and SIGKILL ladder. A one-shot already
had its EOF at the end of its request, so its owner's death still stops it at once.
…r exits on a stop

A requested stop waited out the whole SIGTERM grace for the provider's group even after the
provider itself had exited, so a SIGTERM-ignoring helper it left behind held every stop for
up to 3 s. Under a stop, the rest of the group is now SIGKILLed as soon as the provider has
exited, the same rule its own exit already follows.
…tput limit

Model discovery's supervised stop was covered only under timeout. A Codex discovery that
floods past the output limit now runs through a real supervisor: it settles with the
too-much-data error, its agent is stopped through the supervisor, and the next discovery on
the same Codex home starts only after that agent is gone. The direct-child suites' headers
now list exactly the supervised cases that are covered.
…er closes it

Owner death gave every session provider the stdin-end grace, so after an Orca crash a
Claude session, whose close is a stdin end plus SIGTERM, could keep working on its turn
for a second with nobody watching. The spawn spec now names the provider's close request:
'stdin-end' (the Codex app-server drains and exits on EOF, then gets its grace) or
'stdin-end-and-sigterm' (Claude; the default). A gone owner gets that same request. One
constant per provider feeds both its spawn spec and its owner-side close, through one
requestProviderClose, so the two cannot drift. One-shots still stop at once.
…ts provider exits

Killing the rest of the group the moment the provider exited under a stop also reached
native chat's closes, so an MCP server, a tool's child or a dev server still in Claude's or
Codex's group was SIGKILLed mid-cleanup instead of getting the rest of the SIGTERM grace.
The early group kill now applies only to one-shots, where the saved wait was the point;
a session's stop is back to waiting out the grace for its group.
…itly

The spawn-spec assertion matched the default close request, so dropping Claude's explicit
request still passed. The test now checks that the spec is built with the exit-proof ladder's
own constant.
Other Codex app-server spawns will name the same close request as the connection does.
Holding it in its own small module lets them import it without the connection itself.
Main's #24989 moved the provider supervisor and its teardown into src/main/provider-process/.
PR1's changes move with them: the one-shot lifetime, the provider argv on the supervisor's argv,
the spawn-failure report and its formatter, the no-pid guard, close-request mirroring, the
one-shot-only leader-exit reap and the shared stops. Main's mechanisms replace PR1's where they
overlap: the teardown takes the caller's required site label (PR1's optional site is dropped),
and the provider's env is resolved once by resolveProviderChildEnv before the supervisor keeps
the Node options for the provider alone.
…in the CLI project

The command-line build runs the short-lived Codex app-server session, which will name the
same close request as the Codex connection. Listing the close request, the provider
supervisor it takes its type from, and the spawn-failure report the supervisor uses lets the
CLI project typecheck that import without pulling the connection in.
…e reports one

On Linux, glibc's execvp hands an executable that is not a program to /bin/sh, so both a
direct and a supervised spawn run it and it exits 127; only macOS throws ENOEXEC. The
not-a-program case now runs on macOS only; the path-through-a-file case (ENOTDIR) still
covers a thrown start failure everywhere.
Main's #25204 moved every structured provider's close onto one shared close
(closeProviderProcess, driven by a close policy) and spawn (spawnManagedProviderProcess).
That is the same concept as PR1's stopSupervisedProvider and requestProviderClose, so PR1's
copies are removed and its callers move onto main's:
- The Codex connection and the Claude spawn and exit proof take main's versions.
- The close request a gone owner gets is derived from the provider's close policy inside
  spawnManagedProviderProcess (signalSupervisorOnClose -> 'stdin-end-and-sigterm', else
  'stdin-end'), so one source drives both the owner's close and the supervisor's.
- stopSupervisedChildProcess (agent one-shots) runs main's closeProviderProcess with the
  root-only policy and the caller's close request.
- The Codex close-request constant and its CLI build entries go: nothing uses them now.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 08:49
@coderabbitai

coderabbitai Bot commented Oct 6, 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: 3f308b3d-9767-418d-b28e-4b5284f78b4b
📥 Commits

Reviewing files that changed from the base of the PR and between 29e6696 and dca2bdd.

📒 Files selected for processing (24)
  • src/main/claude/claude-agent-sdk-process-spawn.test.ts
  • src/main/claude/claude-model-catalog-probe.test.ts
  • src/main/claude/claude-stream-json-connection.test.ts
  • src/main/claude/claude-stream-json-connection.ts
  • src/main/codex/codex-app-server-connection.test.ts
  • src/main/codex/codex-app-server-exit-error.test.ts
  • src/main/codex/codex-app-server-exit-error.ts
  • src/main/provider-process/managed-provider-process.ts
  • src/main/provider-process/provider-process-supervisor.integration.test.ts
  • src/main/provider-process/provider-process-supervisor.test.ts
  • src/main/provider-process/provider-process-supervisor.ts
  • src/main/provider-process/provider-spawn-failure-report.test.ts
  • src/main/provider-process/provider-spawn-failure-report.ts
  • src/main/provider-process/supervised-child-process-stop.ts
  • src/main/text-generation/commit-message-model-discovery.ts
  • src/main/text-generation/commit-message-text-generation-cancellation.test.ts
  • src/main/text-generation/commit-message-text-generation-local-subprocess.test.ts
  • src/main/text-generation/commit-message-text-generation-model-discovery.test.ts
  • src/main/text-generation/commit-message-text-generation-test-harness.ts
  • src/main/text-generation/source-control-agent-launch.test.ts
  • src/main/text-generation/source-control-agent-launch.ts
  • src/main/text-generation/source-control-local-process.test.ts
  • src/main/text-generation/source-control-local-process.ts
  • src/main/text-generation/source-control-text-generation-types.ts

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


📝 Walkthrough

Walkthrough

The POSIX provider supervisor now supports session and one-shot lifetimes, configurable close requests, provider-only Node environment settings, and structured spawn-failure reports. Non-Windows source-control agents now launch through the supervisor and use supervised shutdown. Claude and Codex diagnostics format reported spawn failures for display, and local model discovery and source-control processing route those failures to existing error handling.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to dca2b

This change supervises one-shot agent CLI processes on POSIX so they stop when Orca quits or crashes. No concrete merge-blocking issue was identified. Linux behavior has not been verified by the author, which is worth a check in normal QA.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.13% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 32 functions across 24 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the main change: stopping Claude and agent helper processes when Orca exits or crashes. It is specific and relevant, though longer than ideal.
Description check ✅ Passed The description covers the change, rationale, linked issue identifier, visual proof, testing, platform limits, and checklist. It is detailed and mostly complete; the automated checks and known gaps ar…
  • 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 6, 2026 09:05
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Status: reviewed, live-QA'd, CI green

The problem. When Orca quit or crashed while it was briefly running an agent CLI in the background (the Claude model list, model discovery, or commit-message / PR / branch-name generation), nothing stopped that CLI. A CLI that hung kept running after Orca was gone, together with anything it had started.

What changes for you. On macOS and Linux those CLIs, and their children, now stop when Orca quits, crashes or is force-killed. Error messages are unchanged. Windows and WSL behave as before (STA-9579). This is step 1 of 2; step 2 (#25753) covers the short-lived Codex servers and the OpenCode launch preflight.

Review rounds

Round Scope Verdict
1 Lifecycle, and architecture against the references Design kept: one supervisor per one-shot. One P2: on Linux, a prompt of about 89-120 KiB failed with E2BIG because the command rode inside one environment variable. Fixed by passing the command as real arguments, with a size test. Plus P3s: spawn-failure wording, three stop sequences, test gaps. All fixed.
2 Whole-PR sweep, plus native-chat migration No P0-P2. P3s fixed: spawn errors that Node throws immediately, Electron's own startup warning hiding the "not found" message, NODE_OPTIONS reaching the supervisor, crash breadcrumbs filed under the wrong site, honest test labels.
3 Round-2 fixes No P0-P2. P3s fixed: the internal marker leaking into native chat Details, a no-pid spawn (EMFILE) crashing the supervisor, discovery test coverage.
4 Narrow, round-3 fixes No P0-P2. One test-coverage P3, fixed. Converged.
5 Supervisor changes requested by step 2's review No P0-P2. P3 fixed: the group kill after the CLI exits now applies to one-shots only, so native chat's MCP servers and tool children keep their SIGTERM cleanup grace. The Claude close-request wiring is now pinned by a test.
Merge Port onto main's provider-process layout (#24989) No findings. Main's mechanisms are reused, with no second copy.
Merge Main's shared provider close (#25204) This PR's own close sequence was removed in favour of main's closeProviderProcess. Owner death now mirrors each provider's close policy: Claude stops at once, Codex drains for 1 s.

Each fix was ablated: undoing it makes its test fail.

Live QA (macOS, isolated rig, stand-in CLI, driven by Grok)

Build Quit Crash
main 17ecee6 stand-in and grandchild still alive 20.8 s after the quit still alive after 20 s
This PR gone in 0.48 s gone in 0.37-0.38 s

main: 20.8 s after Orca quit, the stand-in claude and its sleep grandchild are still alive
main: after Orca is killed, both are still alive when the 20 s watch ends
this PR: everything is gone 0.48 s after the quit
this PR: everything is gone 0.37-0.38 s after the kill

The real ~/.codex/hooks.json, ~/.codex/config.toml and ~/.claude/settings.json were hashed before the run, after the first launch and at the end, and never changed. ~/.claude.json changed only from the other Claude sessions on the machine; no rig process held it.

CI

Head dca2bdde14d: 13 passed, 17 skipped, 0 failed. None of main's known failures appeared (the win32 prompt-carry test, cross-version @streamparser/json, the STA-9548 e2e). One earlier red on a previous head was this PR's own test, which assumed macOS behaviour: a file that isn't a program only throws ENOEXEC on macOS. That case is now macOS-only, and the PR's Linux behaviour matches main's.

Not verified

  • The Linux runtime. The E2BIG fix is covered by a unit test of argument and environment sizes; macOS has no per-string limit.
  • The real Claude, Codex and OpenCode CLIs. Every test and the live QA used stand-ins.
  • Windows behaviour, beyond CI's Windows package job. Windows and WSL are unchanged by design.
  • The live QA ran on ed5201b1c84. Later commits changed only session-mode closes and the merges with main; the one-shot quit and crash path the QA covers is the same.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 10:21
@brennanb2025
brennanb2025 merged commit 19661df into main Oct 6, 2026
78 checks passed
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
…de-probes

#25715 (step 1) landed on main as 19661df; this brings PR 2 onto main.
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