diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 155f1e7..3d3a199 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -130,17 +130,19 @@ async def _resolve_single_git_mount( import hashlib url_hash = hashlib.md5(remote_url.encode()).hexdigest()[:12] repo_name = remote_url.split("/")[-1].replace(".git", "") or "repo" - clone_dir = os.path.join(instance_dir, "git-mounts", f"{repo_name}-{url_hash}") + clone_parent = os.path.join(instance_dir, "git-mounts", f"{repo_name}-{url_hash}") + # clone_repository always creates 'repo-clone' inside the given directory + repo_path = os.path.join(clone_parent, "repo-clone") # Clone or pull the repository - repo_path = clone_dir - if not os.path.exists(clone_dir): + if not os.path.exists(repo_path): try: + os.makedirs(clone_parent, exist_ok=True) repo_path = await asyncio.to_thread( clone_repository, remote_url, None, # No SSH key for now - can be added later - os.path.dirname(clone_dir), + clone_parent, branch or "main", ) logger.debug("Cloned git mount repository %s to %s", remote_url, repo_path) @@ -384,6 +386,47 @@ async def _validate_config_profile( return profile_uuid +def _sanitize_compose_file(compose_path: str) -> None: + """Remove invalid port mappings (target port 0) from compose file.""" + import yaml + from pathlib import Path + + compose_file = Path(compose_path) + if not compose_file.exists(): + return + + content = compose_file.read_text() + compose_data = yaml.safe_load(content) + + if not compose_data or "services" not in compose_data: + return + + modified = False + for service_name, service_config in compose_data["services"].items(): + if "ports" in service_config: + valid_ports = [] + for port_mapping in service_config["ports"]: + if isinstance(port_mapping, str) and ":" in port_mapping: + parts = port_mapping.split(":") + if len(parts) == 2: + host_port, container_port = parts + # Skip invalid mappings (target port 0 or empty) + if container_port == "0" or not container_port: + modified = True + continue + valid_ports.append(port_mapping) + + if valid_ports: + service_config["ports"] = valid_ports + else: + del service_config["ports"] + modified = True + break # Only check first service + + if modified: + compose_file.write_text(yaml.dump(compose_data, default_flow_style=False)) + + def _modify_compose_file( compose_path: str, port_override: int | None = None, @@ -996,6 +1039,9 @@ async def start_instance( _modify_compose_file(instance.compose_path, port_override, start_command, working_directory, extra_volumes) logger.debug("Modified compose file for instance %s", instance.id) + # Sanitize compose file to remove invalid port mappings from old instances + _sanitize_compose_file(instance.compose_path) + # Execute docker compose up with env file logger.debug("Running docker compose up for instance %s (compose_path=%s)", instance.id, instance.compose_path) returncode, stdout, stderr = execute_compose_command(