diff --git a/server/services/dev_server_manager.py b/server/services/dev_server_manager.py index 063e076..5acfbc8 100644 --- a/server/services/dev_server_manager.py +++ b/server/services/dev_server_manager.py @@ -428,7 +428,9 @@ class DevServerProcessManager: # Global registry of dev server managers per project with thread safety -_managers: dict[str, DevServerProcessManager] = {} +# Key is (project_name, resolved_project_dir) to prevent cross-project contamination +# when different projects share the same name but have different paths +_managers: dict[tuple[str, str], DevServerProcessManager] = {} _managers_lock = threading.Lock() @@ -444,18 +446,11 @@ def get_devserver_manager(project_name: str, project_dir: Path) -> DevServerProc DevServerProcessManager instance for the project """ with _managers_lock: - if project_name in _managers: - manager = _managers[project_name] - # Update project_dir in case project was moved - if manager.project_dir.resolve() != project_dir.resolve(): - logger.info( - f"Project {project_name} path updated: {manager.project_dir} -> {project_dir}" - ) - manager.project_dir = project_dir - manager.lock_file = project_dir / ".devserver.lock" - return manager - _managers[project_name] = DevServerProcessManager(project_name, project_dir) - return _managers[project_name] + # Use composite key to prevent cross-project UI contamination (#71) + key = (project_name, str(project_dir.resolve())) + if key not in _managers: + _managers[key] = DevServerProcessManager(project_name, project_dir) + return _managers[key] async def cleanup_all_devservers() -> None: diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 350905f..692c946 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -510,7 +510,9 @@ class AgentProcessManager: # Global registry of process managers per project with thread safety -_managers: dict[str, AgentProcessManager] = {} +# Key is (project_name, resolved_project_dir) to prevent cross-project contamination +# when different projects share the same name but have different paths +_managers: dict[tuple[str, str], AgentProcessManager] = {} _managers_lock = threading.Lock() @@ -523,9 +525,11 @@ def get_manager(project_name: str, project_dir: Path, root_dir: Path) -> AgentPr root_dir: Root directory of the autonomous-coding-ui project """ with _managers_lock: - if project_name not in _managers: - _managers[project_name] = AgentProcessManager(project_name, project_dir, root_dir) - return _managers[project_name] + # Use composite key to prevent cross-project UI contamination (#71) + key = (project_name, str(project_dir.resolve())) + if key not in _managers: + _managers[key] = AgentProcessManager(project_name, project_dir, root_dir) + return _managers[key] async def cleanup_all_managers() -> None: