From 60e1280d70cc93dc9aeb22847fc9fc2335f7554a Mon Sep 17 00:00:00 2001 From: genisis0x Date: Fri, 15 May 2026 15:53:11 +0530 Subject: [PATCH] fix(env): keep container bind paths POSIX in normalize_volumes On Windows hosts, normalize_volumes() was passing both halves of each bind mount through os.path.abspath/os.path.join. For absolute container-side paths such as '/workspace/qlib_workspace', this rewrites them to native Windows paths like 'C:\workspace\qlib_workspace', and the resulting Docker bind string C:\Users\...\work_dir:C:\workspace\qlib_workspace:rw contains too many colons for Docker to parse, raising: 500 Internal Server Error: invalid mount config ... mount denied Split the helper in two: - Host keys go through os.path.abspath so a relative '.' / '~' style entry still resolves to a native absolute path the Docker SDK understands on every platform. - Container paths go through PurePosixPath so they keep forward slashes regardless of the host OS. Relative container paths are resolved against working_dir using POSIX semantics, which matches what the container actually sees. Verified by users on the issue thread that this resolves the Windows mount failure without affecting Linux or WSL behaviour. Fixes #1064 --- rdagent/utils/env.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/rdagent/utils/env.py b/rdagent/utils/env.py index 47d5aea2b..2c19b0d4f 100644 --- a/rdagent/utils/env.py +++ b/rdagent/utils/env.py @@ -22,7 +22,7 @@ from collections import deque from dataclasses import dataclass from datetime import datetime -from pathlib import Path +from pathlib import Path, PurePosixPath from types import MappingProxyType from typing import ( Any, @@ -113,26 +113,35 @@ def cleanup_container(container: docker.models.containers.Container | None, cont logger.warning(f"Failed to cleanup{context_str} container {container.id}: {cleanup_error}") -# Normalize all bind paths in volumes to absolute paths using the workspace (working_dir). +# Normalize all bind paths in volumes to absolute paths. +# Host keys are resolved to native absolute paths so Docker on Windows +# receives `C:\...` rather than a POSIX-style key, while container-side +# paths are forced to POSIX so they don't get rewritten as `C:\workspace\...` +# on Windows (which produced "too many colons" mount errors — see #1064). def normalize_volumes(vols: dict[str, str | dict[str, str]], working_dir: str) -> dict: abs_vols: dict[str, str | dict[str, str]] = {} - def to_abs(path: str) -> str: - # Converts a relative path to an absolute path using the workspace (working_dir). - return os.path.abspath(os.path.join(working_dir, path)) if not os.path.isabs(path) else path + def to_abs_host(path: str) -> str: + return os.path.abspath(path) + + def to_abs_posix_container(path: str) -> str: + # Container paths are always POSIX. Absolute → keep, relative → resolve + # against the container-side working directory. + if os.path.isabs(path): + return str(PurePosixPath(path)) + return str(PurePosixPath(working_dir) / path) for lp, vinfo in vols.items(): # Support both: # 1. {'host_path': {'bind': 'container_path', ...}} # 2. {'host_path': 'container_path'} + abs_host = to_abs_host(lp) if isinstance(vinfo, dict): - # abs_vols = cast(dict[str, dict[str, str]], abs_vols) vinfo = vinfo.copy() - vinfo["bind"] = to_abs(vinfo["bind"]) - abs_vols[lp] = vinfo + vinfo["bind"] = to_abs_posix_container(vinfo["bind"]) + abs_vols[abs_host] = vinfo else: - # abs_vols = cast(dict[str, str], abs_vols) - abs_vols[lp] = to_abs(vinfo) + abs_vols[abs_host] = to_abs_posix_container(vinfo) return abs_vols