Skip to content

code_run stop/timeout leaves descendant processes running #797

Description

@Whxuan0701

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):

  1. Call code_run() with a parent script that starts a child process.
  2. Let the child sleep, then write a canary file.
  3. Trigger stop_signal or a short timeout before the child writes.
  4. 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().

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions