Windows hook timeout can leave alias descendants alive and async hook lifecycle events are not published
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Environment
- Codex
rust-v0.154.0 - Base commit
6b9826e3aa83b1a5947db50f4332cb9c65f1b340 - Windows 11 x64
Symptoms
- A command hook launched through the Windows
python3App Execution Alias starts a child process. - Timeout or async runtime cancellation returns, but a descendant outside the job remains alive.
- The current public hook summary does not provide a local record that joins the effective command, run ID, execution instance, exit/timeout/cancellation result, and partial stderr.
- Non-builtin async hook runs do not emit the public
HookStartedandHookCompletedevents emitted for synchronous runs.
Reproduction
The reproduction uses an ignored Rust hook test plus a Python observer that opens process handles for the shell launcher, fixture parent, and leaf before allowing the hook to proceed. The observer has sole ownership of the rescue release file and keeps an unrelated sentinel alive.
On an unpatched control, the alias timeout/cancellation case reaches the six-second rescue path with owned descendants still alive. With the proposed cleanup ordering, the owned tree exits before rescue. On normal exit 0 or 7, the leaf remains alive until observer cleanup, preserving the current detached-helper behavior.
Root cause
Child::wait_with_output() owns the child. When the timeout future is dropped, Tokio's kill-on-drop behavior can remove the shell before ProcessTreeGuard performs fallback tree enumeration. Terminating the job first also misses alias descendants outside that job.
Proposed approach
- Borrow the child while waiting and draining output.
- Retain the root process handle to prevent PID reuse during cleanup.
- On abnormal exit, enumerate and terminate the tree before terminating the job.
- Keep the existing normal-completion descendant preservation path.
- Add an explicit local evidence gate at the command runner and publish non-builtin async lifecycle events.
Evidence
- Ordinary hook suite: 182 passed, 1 ignored
- Focused async lifecycle test: passed
- Windows fixed matrix: 16 combinations, no abnormal-case rescue, all normal-case leaf preservation checks passed
- Mailbox patch and full reproduction instructions are available on request
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 at the command runner and the Child::wait_with_output()/ProcessTreeGuard paths described in the issue; compare synchronous and non-builtin async hook execution. Run the ignored Rust hook reproduction and focused async lifecycle test on Windows, then verify abnormal timeout/cancellation cleanup, normal descendant preservation, local evidence, and HookStarted/HookCompleted publication. Done means the ordinary hook suite and fixed matrix retain the reported passing behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- devtools, 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