Windows Desktop: root-only abort races taskkill /T, leaving Git descendants alive
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Summary
Codex Desktop's Windows local-execution cancellation path appears to register two handlers on the same AbortSignal: the low-level handler kills only the root process, while the higher-level handler subsequently tries to terminate the tree using taskkill /pid <root-pid> /t /f.
If the first handler removes the root before taskkill enumerates it, taskkill exits with code 128 ("process not found"), while Git descendants remain alive. An isolated A/B test reproduced this failure using unmodified Git for Windows.
Please fix cancellation ownership and add a regression test that verifies descendant termination, not just a cancelled command result.
Environment and verification scope
- Windows 11 x64.
- Codex Desktop/MSIX 26.903.9818.0: packaged code inspected; cancellation order reproduced independently.
- Codex Desktop/MSIX 26.908.4834.0: packaged worker code inspected; the same two-handler arrangement remains present. The full reproduction was not rerun against this newer build.
- Git for Windows 2.54.0.windows.1.
This concerns the desktop local execution host, including internal Git status/review work; it is not a model-output or global AGENTS.md concurrency issue.
Implementation observations
In the desktop worker's local execution host:
- The spawn layer forwards
signal: options.signalto a lower-level subprocess wrapper. - That wrapper registers an abort listener which calls
child.kill(). - The local execution host also registers an abort listener which invokes its execution object's kill method.
- When
killProcessTreeis enabled, the latter invokes taskkill against the original root PID.
The earlier build also allowed cancellation to be reported before tree cleanup completed and suppressed cleanup failures. The newer build appears to await a pending cleanup promise in its wait path, but still retains the root-kill/tree-kill ordering hazard. Please inspect both cancellation ownership and cleanup error handling.
These are observations from locally inspected packaged code, not a claim about an identified public-source commit.
Isolated reproduction
Use a disposable repository and a Node.js harness on Windows. Do not run this as a stress test.
- Spawn the standard Git for Windows command wrapper with argument array:
['-c', 'alias.repro=!sleep 10', 'repro'].
The alias is supplied only for this invocation; no persistent Git configuration is needed. - Use piped stdin/stdout/stderr and
windowsHide: true. Allow descendants to start and record only this test process's descendants. - A — existing ordering: call
child.kill(), then directly invoketaskkill.exewith['/pid', String(child.pid), '/t', '/f']using execFile (no shell). - B — control: repeat with a fresh test process, invoking tree termination without the preceding root-only kill.
- Inspect the recorded descendants promptly, before the ten-second sleep ends. Clean up only the test's recorded processes.
Observed:
| Case | taskkill result | Root | Descendants after cancellation |
|---|---|---|---|
| A: root kill followed by tree kill | 128, process not found | exited | git.exe, sh.exe, sleep.exe and conhost.exe remained alive |
| B: tree kill only | success | exited | none remained |
In case A, the Node child close event had also not fired at the observation point. All test descendants were explicitly cleaned up afterward.
This establishes the failure mechanism independently; it does not establish its frequency in ordinary desktop sessions.
Impact and limits of attribution
The investigation began after repeated system-wide stalls during Codex use and internal Git operations timing out. Local crash-dump counters independently confirmed system commit exhaustion. However, historical per-process totals are unavailable, so the cancellation bug has not been proven to account for all memory consumption or the full Windows freezes.
A later experimental local process-tree wrapper did not resolve the incidents and was removed. Its subsequent out-of-memory failures should not be attributed to unmodified Git or used as independent proof of the desktop bug. The isolated reproduction above used standard Git.
Requested fix and regression coverage
- Give cancellation one owner when process-tree termination is requested; prevent the lower layer from independently killing the root first.
- Await bounded cleanup and surface failures.
- Verify that descendants are gone after abort and timeout, including descendants retaining inherited output handles.
- Test both live-root cancellation and unexpected root exit.
- Bound cleanup-helper concurrency and execution time so failed cleanup cannot create another process storm.
Related reports: #34260 and #33776 describe Windows cleanup-helper accumulation. This report adds a distinct, reproduced root-first termination race; it does not assume those reports share exactly the same cause.
Privacy
Text-only technical report. No raw logs, crash dumps, screenshots, conversation/session IDs, repository content, private project names, user-specific paths, hostnames, network addresses, credentials, or environment-variable values are included.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in the desktop worker's local execution host, the spawn layer's subprocess wrapper, and the execution object's kill method; trace how AbortSignal listeners and taskkill.exe are coordinated. Reproduce the live-root and unexpected-root-exit cases with the supplied Windows Node.js harness. Done means descendants are gone after cancellation or timeout, cleanup is awaited and failures are surfaced, and cleanup remains bounded.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, node.js, rust
- Domain
- desktop, operating-systems, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100