code_run stop/timeout leaves descendant processes running
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_signalor 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
SIGTERMtoSIGKILLafter a bounded wait. - Windows: place the spawned process in a Job Object and terminate the job; retain
taskkill /T /Fas 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().
Source: lsdefine/GenericAgent