Skip to content

fix(coding-agent): reap orphaned process trees during Windows worker recovery - #1

Open
grimmjoww wants to merge 3 commits into
WDDGDA:feat/windows-native-supportfrom
grimmjoww:feat/windows-744-delta
Open

fix(coding-agent): reap orphaned process trees during Windows worker recovery#1
grimmjoww wants to merge 3 commits into
WDDGDA:feat/windows-native-supportfrom
grimmjoww:feat/windows-744-delta

Conversation

@grimmjoww

@grimmjoww grimmjoww commented Aug 8, 2026

Copy link
Copy Markdown

Stacked on @WDDGDA's PrimeIntellect-ai#744. This fixes one confirmed gap left in the Windows recovery path, filed upstream as PrimeIntellect-ai#917.

On Windows, reapOrphanProcesses polls journaled orphan processes but never signals them. Orphans spawned outside the worker's kill-on-close Job Object (autonomous-mode children) survive a worker crash, so recovery burns three attempts waiting for processes nobody is killing, then fails with One or more orphaned process trees could not be terminated.

The win32 path now signals each identity-current orphan with the existing signalProcessTree (taskkill /F /T /PID, hidden) before polling, the same signal-then-verify shape as the POSIX path. The identity check sits right next to the signal so a recycled PID never gets taskkilled by mistake.

Test

test/suite/regressions/917-reap-orphans-windows.test.ts, win32-only, per the repo's regression convention. It boots a real supervisor and resident worker on a named pipe (offline, no API keys), plants a live process journaled as the worker's orphan but outside the Job Object, SIGKILLs the worker, and requires recovery to actually terminate the orphan. Process-start-id checks keep PID reuse from faking a pass.

Red before the fix: three Could not reap orphaned worker resources log lines, then Worker failed after three recovery attempts, orphan still alive. Green after, about 6-8s. One scoping note: the fixture is a single process, so this covers the journal-to-signal path that actually broke; taskkill's tree walk is older utility code and isn't separately exercised here.

What I ran

Windows 11 x64, Node 24.18.0, npm 11.12.1, Git Bash at the standard location. Package-root vitest runs per repo AGENTS.md, PI_NO_LOCAL_LLM=1, provider key env unset, no real keys anywhere.

  • New regression red-to-green, and green again inside the full suite (~20s under load).
  • test/daemon-supervisor-process.test.ts: 8 passed, 6 skipped.
  • Targeted Windows files (windows-job-object, bash-close-hang-windows, git-context-windows, clipboard-image): 12/12.
  • npm run check: clean (biome, tsgo, both installer checks, browser smoke).
  • Full coding-agent suite on this machine: 87 failed / 3918 passed (87 unique failed tests; 95 raw failure lines counting retries). @FlamesONE measured 137 failing on main on their Windows 11 box, so this branch plus the fix cuts that by about a third. The 95 raw lines break down as roughly 56 kernel/python provisioning, 28 behavioral (a lot of those are POSIX literals baked into test data, like file:/tmp/...), 7 environment (hardlink EPERM on the Program Files node.exe, /tmp socket paths), and a few flakes. None intersect this change. The one recovery-adjacent failure, 4603-worker-recovery, dies in test setup trying to hardlink C:\Program Files\nodejs\node.exe, before any supervisor exists.

I also ran install.ps1 end to end locally: packed the branch release, served it over a local HTTP endpoint with the base-URL and channel sentinels filled in, installed through Windows PowerShell 5.1. Checksum verified, per-user npm-global install, kernel venv provisioned, and prime-agent --version prints 0.7.0 from a fresh shell. Local builds were required for that validation; the AGENTS.md no-build default is about dev loops, which this was not.

Checked but not changed

  • fix(coding-agent): rebuild the kernel venv while a kernel is running on Windows PrimeIntellect-ai/prime-agent#763 (venv rebuild EPERM with a running kernel): still reproduces by construction on this branch (bootstrap.ts does rm(venv, { recursive: true }) over a mapped executable). That fix belongs to that PR; no overlap here.
  • @lennix1337's clipboard follow-up (0ef7b8e) is already in this branch head. Clipboard tests pass.
  • @khanra17's Git-Bash-from-git.exe installer idea: not reproducible on this machine (Git Bash is in the standard place), so I didn't patch it blind. Still looks worth doing for nonstandard layouts.
  • Two observations from running the daemon with piped stdout (my harness, so probably launch-context noise rather than user-facing): a worker received a shutdown command seconds after start while status/agents probes were running, and worker shutdown logged Failed to sync '<stdout>': Incorrect function. Flagging in case either rings a bell.

There's a CHANGELOG bullet in the diff per repo convention.

…recovery

On Windows, reapOrphanProcesses only polled journaled orphans and never
signaled them. Orphan trees spawned outside the worker's kill-on-close Job
Object (autonomous children) survived the worker, so recovery failed after
three attempts with "One or more orphaned process trees could not be
terminated".

Signal each identity-current orphan tree with signalProcessTree
(taskkill /F /T) before polling for disappearance, matching the POSIX
path's semantics.

Add a win32 regression test that plants a journaled, non-Job-assigned
orphan, SIGKILLs the worker, and requires recovery to terminate it.

fixes PrimeIntellect-ai#917
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b17a2504-f4c5-477a-aea3-68690b3ec2f8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26bc2093a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +102 to +104
const child = spawn(
process.execPath,
[

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route this regression through the required suite harness

Because this test is under test/suite/, manually spawning the CLI bypasses the repository-required test/suite/harness.ts and faux provider setup. Move this process-level test outside the suite tree or extend the harness to support this daemon-recovery scenario so it uses the standard isolated lifecycle and provider configuration.

AGENTS.md reference: AGENTS.md:L32-L32

Useful? React with 👍 / 👎.

Comment on lines +2715 to +2716
if (isOrphanProcessIdentityCurrent(orphan)) {
signalProcessTree(orphan.pid, "SIGKILL");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Eliminate the PID-reuse race before taskkill

If the journaled process exits after this second identity check but before signalProcessTree starts taskkill.exe, Windows can recycle the PID and the PID-only /F /T /PID invocation will terminate an unrelated process tree. The extra check narrows the race but does not provide the claimed identity safety; hold a process handle across start-ID validation and termination so the PID cannot be reused in between.

Useful? React with 👍 / 👎.

Comment on lines +2714 to +2716
for (const orphan of remaining) {
if (isOrphanProcessIdentityCurrent(orphan)) {
signalProcessTree(orphan.pid, "SIGKILL");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid blocking the supervisor once per orphan

When a crashed worker has multiple live journal entries, this sequential loop invokes the Windows signalProcessTree implementation, which uses spawnSync with a separate five-second timeout for every taskkill.exe call. Because no await occurs until all signals finish, the deadline above does not bound this phase and the supervisor can stop serving every daemon client for roughly five seconds times the orphan count; use asynchronous/concurrent termination or enforce one shared deadline.

Useful? React with 👍 / 👎.

@grimmjoww

Copy link
Copy Markdown
Author

Post-review notes from an independent closeout pass (all bounded, none blocking):

  1. The 5-second reap deadline is computed before the synchronous signalProcessTree loop. Each taskkill is a blocking spawnSync with its own 5s timeout, so several hung taskkills could consume the poll budget before the first iteration and produce a false-negative reap failure. It self-heals on the next recovery attempt. If maintainers prefer, the deadline can be moved after the signal loop in a follow-up; I left the shipped shape as-is to match the POSIX path's overall structure.

  2. Orphan journal records written without a processStartId are silently dropped by readActiveOrphanProcesses — never signaled, never awaited, and reap returns success on them. This filter predates this change; the new win32 path inherits it. Flagging in case it deserves its own issue upstream.

  3. Adjacent good news for fix(coding-agent): rebuild the kernel venv while a kernel is running on Windows PrimeIntellect-ai/prime-agent#763: terminating journaled kernel-descendant orphans during recovery releases mapped python.exe handles, which should marginally shrink the venv-rebuild EPERM window on this branch. No action, just a nice interaction.

MiniMax-M3 was explicitly filtered out from the minimax/minimax-cn
providers due to an outdated supported-ids allowlist. Added to
minimaxDirectSupportedIds and the context window override logic
updated so M3 keeps its native 1M context window instead of
inheriting M2.7's 204800 limit. Regenerated model catalog.

Also fixed pre-existing TS errors in context-overflow and
total-tokens tests by updating deprecated gemini-2.0-flash
model reference to gemini-2.5-flash.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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