diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index 486b963..60b4489 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -504,14 +504,20 @@ class ParallelOrchestrator: cmd.append("--yolo") try: - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - cwd=str(AUTOCODER_ROOT), - env={**os.environ, "PYTHONUNBUFFERED": "1"}, - ) + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + # stdin=DEVNULL prevents blocking on stdin reads + 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() @@ -703,6 +709,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 _signal_agent_completed(self): diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 692c946..fd1a192 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -349,14 +349,20 @@ class AgentProcessManager: try: # Start subprocess with piped stdout/stderr # Use project_dir as cwd so Claude SDK sandbox allows access to project files - # IMPORTANT: Set PYTHONUNBUFFERED to ensure output isn't delayed - self.process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd=str(self.project_dir), - env={**os.environ, "PYTHONUNBUFFERED": "1"}, - ) + # stdin=DEVNULL prevents blocking if Claude CLI or child process tries to read stdin + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + # PYTHONUNBUFFERED ensures output isn't delayed + popen_kwargs = { + "stdin": subprocess.DEVNULL, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "cwd": str(self.project_dir), + "env": {**os.environ, "PYTHONUNBUFFERED": "1"}, + } + 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