From d48fb0a6fce2a8a3a228e706e7c9e80817f80cb3 Mon Sep 17 00:00:00 2001 From: mmereu Date: Sat, 24 Jan 2026 10:40:47 +0100 Subject: [PATCH 1/2] fix: prevent agent subprocess blocking on Windows - Add stdin=subprocess.DEVNULL to prevent blocking on stdin reads - Add CREATE_NO_WINDOW flag on Windows to prevent console pop-ups - Remove trailing pause from start_ui.bat Co-Authored-By: Claude Opus 4.5 --- parallel_orchestrator.py | 21 +++++++++++++-------- server/services/process_manager.py | 18 ++++++++++++------ start_ui.bat | 2 -- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index da348c8..91e7be0 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -299,14 +299,19 @@ class ParallelOrchestrator: cmd.append("--yolo") try: - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - cwd=str(AUTOCODER_ROOT), # Run from autocoder root for proper imports - env={**os.environ, "PYTHONUNBUFFERED": "1"}, - ) + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + popen_kwargs = { + "stdin": subprocess.DEVNULL, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "text": True, + "cwd": str(AUTOCODER_ROOT), # Run from autocoder root for proper imports + "env": {**os.environ, "PYTHONUNBUFFERED": "1"}, + } + if sys.platform == "win32": + popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + proc = subprocess.Popen(cmd, **popen_kwargs) except Exception as e: # Reset in_progress on failure session = self.get_session() diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 2dc1137..01b9b47 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -341,12 +341,18 @@ class AgentProcessManager: try: # Start subprocess with piped stdout/stderr # Use project_dir as cwd so Claude SDK sandbox allows access to project files - self.process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd=str(self.project_dir), - ) + # stdin=DEVNULL prevents blocking if Claude CLI or child process tries to read stdin + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + popen_kwargs = { + "stdin": subprocess.DEVNULL, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "cwd": str(self.project_dir), + } + if sys.platform == "win32": + popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + self.process = subprocess.Popen(cmd, **popen_kwargs) # Atomic lock creation - if it fails, another process beat us if not self._create_lock(): diff --git a/start_ui.bat b/start_ui.bat index 2c59753..c8ad646 100644 --- a/start_ui.bat +++ b/start_ui.bat @@ -39,5 +39,3 @@ pip install -r requirements.txt --quiet REM Run the Python launcher python "%~dp0start_ui.py" %* - -pause From 795bd5f92a8980244f6c093c38e4273b00eba678 Mon Sep 17 00:00:00 2001 From: mmereu Date: Sat, 24 Jan 2026 11:18:28 +0100 Subject: [PATCH 2/2] fix: kill process tree on agent completion to prevent zombies Added _kill_process_tree call in _read_output finally block to ensure child processes (Claude CLI) are cleaned up when agents complete or fail. This prevents accumulation of zombie processes that was causing 78+ Python processes when max concurrency was set to 5. Co-Authored-By: Claude Opus 4.5 --- parallel_orchestrator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index 74ce84f..98778c1 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -632,6 +632,12 @@ class ParallelOrchestrator: print(f"[Feature #{feature_id}] {line}", flush=True) proc.wait() finally: + # CRITICAL: Kill the process tree to clean up any child processes (e.g., Claude CLI) + # This prevents zombie processes from accumulating + try: + _kill_process_tree(proc, timeout=2.0) + except Exception as e: + debug_log.log("CLEANUP", f"Error killing process tree for {agent_type} agent", error=str(e)) self._on_agent_complete(feature_id, proc.returncode, agent_type, proc) def _on_agent_complete(