Problem
code_run() kills only the direct Popen child on timeout or manual stop. Commands that launch their own subprocesses can therefore keep running after GenericAgent reports [Timeout Error] or [Stopped].
This is observable with normal agent workloads: a Python tool can launch a compiler, browser, worker pool, or shell pipeline. Stopping the tool should revoke that whole execution scope, not only its first process.
Reproduction
Tested on main@f6e5657 (macOS 14.6.1, Python 3.13.14):
- Call
code_run() with a parent script that starts a child process.
- Let the child sleep, then write a canary file.
- Trigger
stop_signal or a short timeout before the child writes.
- Wait past the child's sleep.
Minimal payload:
child = "import pathlib,time; time.sleep(2); pathlib.Path('orphan-canary').write_text('orphaned')"
parent = f"import subprocess,sys,time; subprocess.Popen([sys.executable, '-c', {child!r}]); time.sleep(30)"
Observed: GenericAgent returns the stop/timeout marker, but orphan-canary appears afterwards. The same behavior occurs for both manual stop and timeout.
Root cause
The abort path calls process.kill() in ga.py. That terminates only the direct process and does not address descendants that inherited the stdout pipe or continue independently.
Expected behavior
Timeout and manual stop should terminate the complete process tree started for that code_run() invocation.
Suggested direction
- POSIX: start each run in a new session and signal its process group, escalating from
SIGTERM to SIGKILL after a bounded wait.
- Windows: place the spawned process in a Job Object and terminate the job; retain
taskkill /T /F as a fallback.
- Preserve normal successful-process behavior and the existing returned status markers.
- Add regression tests where a child attempts a delayed side effect after stop/timeout.
This is narrower than #396 (WeChat output after /stop) and #332 (conversation responsiveness after timeout): the failure is specifically the lifecycle of descendants created by code_run().
Problem
code_run()kills only the directPopenchild on timeout or manual stop. Commands that launch their own subprocesses can therefore keep running after GenericAgent reports[Timeout Error]or[Stopped].This is observable with normal agent workloads: a Python tool can launch a compiler, browser, worker pool, or shell pipeline. Stopping the tool should revoke that whole execution scope, not only its first process.
Reproduction
Tested on
main@f6e5657(macOS 14.6.1, Python 3.13.14):code_run()with a parent script that starts a child process.stop_signalor a short timeout before the child writes.Minimal payload:
Observed: GenericAgent returns the stop/timeout marker, but
orphan-canaryappears afterwards. The same behavior occurs for both manual stop and timeout.Root cause
The abort path calls
process.kill()inga.py. That terminates only the direct process and does not address descendants that inherited the stdout pipe or continue independently.Expected behavior
Timeout and manual stop should terminate the complete process tree started for that
code_run()invocation.Suggested direction
SIGTERMtoSIGKILLafter a bounded wait.taskkill /T /Fas a fallback.This is narrower than #396 (WeChat output after
/stop) and #332 (conversation responsiveness after timeout): the failure is specifically the lifecycle of descendants created bycode_run().