Skip to content

Commit 162c8d9

Browse files
author
Agent-Planner
committed
Merge PR AutoForgeAI#89: Prevent agent subprocess blocking on Windows
2 parents e8f271d + 795bd5f commit 162c8d9

3 files changed

Lines changed: 25 additions & 11 deletions

File tree

parallel_orchestrator.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,20 @@ def _spawn_coding_agent(self, feature_id: int) -> tuple[bool, str]:
539539
cmd.append("--yolo")
540540

541541
try:
542-
proc = subprocess.Popen(
543-
cmd,
544-
stdout=subprocess.PIPE,
545-
stderr=subprocess.STDOUT,
546-
text=True,
547-
cwd=str(AUTOCODER_ROOT),
548-
env={**os.environ, "PYTHONUNBUFFERED": "1"},
549-
)
542+
# CREATE_NO_WINDOW on Windows prevents console window pop-ups
543+
# stdin=DEVNULL prevents blocking on stdin reads
544+
popen_kwargs = {
545+
"stdin": subprocess.DEVNULL,
546+
"stdout": subprocess.PIPE,
547+
"stderr": subprocess.STDOUT,
548+
"text": True,
549+
"cwd": str(AUTOCODER_ROOT), # Run from autocoder root for proper imports
550+
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
551+
}
552+
if sys.platform == "win32":
553+
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
554+
555+
proc = subprocess.Popen(cmd, **popen_kwargs)
550556
except Exception as e:
551557
# Reset in_progress on failure
552558
session = self.get_session()
@@ -735,6 +741,12 @@ def _read_output(
735741
print(f"[Feature #{feature_id}] {line}", flush=True)
736742
proc.wait()
737743
finally:
744+
# CRITICAL: Kill the process tree to clean up any child processes (e.g., Claude CLI)
745+
# This prevents zombie processes from accumulating
746+
try:
747+
_kill_process_tree(proc, timeout=2.0)
748+
except Exception as e:
749+
debug_log.log("CLEANUP", f"Error killing process tree for {agent_type} agent", error=str(e))
738750
self._on_agent_complete(feature_id, proc.returncode, agent_type, proc)
739751

740752
def _signal_agent_completed(self):

server/services/process_manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,18 +411,22 @@ async def start(
411411
# Start subprocess with piped stdout/stderr
412412
# Use project_dir as cwd so Claude SDK sandbox allows access to project files
413413
# IMPORTANT: Set PYTHONUNBUFFERED to ensure output isn't delayed
414+
# stdin=DEVNULL prevents blocking if Claude CLI or child process tries to read stdin
414415

415416
# On Windows, use CREATE_NEW_PROCESS_GROUP for better process tree management
416417
# This allows taskkill /T to reliably kill all child processes
417418
popen_kwargs = {
419+
"stdin": subprocess.DEVNULL,
418420
"stdout": subprocess.PIPE,
419421
"stderr": subprocess.STDOUT,
420422
"cwd": str(self.project_dir),
421423
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
422424
}
423425
if sys.platform == "win32":
426+
# CREATE_NEW_PROCESS_GROUP enables reliable process tree termination
427+
# CREATE_NO_WINDOW could be added but conflicts with process group
424428
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
425-
429+
426430
self.process = subprocess.Popen(cmd, **popen_kwargs)
427431

428432
# Atomic lock creation - if it fails, another process beat us

start_ui.bat

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,3 @@ pip install -r requirements.txt --quiet
3939

4040
REM Run the Python launcher
4141
python "%~dp0start_ui.py" %*
42-
43-
pause

0 commit comments

Comments
 (0)