fix: git repo mount race and path resolution

- Include branch in git-mount clone dir hash so different branches of the
  same repo get separate directories and no longer race.
- Resolve profile working_directory before git mounts so relative targets
  are not silently skipped.
- Fall back to tool_type.home_directory for non-manifest tools.
- Fix glob target calculation to avoid duplicate directory segment.
- Add exc_info logging for git mount clone failures.
This commit is contained in:
Developer
2026-06-15 12:15:50 +00:00
parent 29e48cdb65
commit 6a61669294
+46 -12
View File
@@ -204,7 +204,12 @@ def clone_git_repo(
"""
import hashlib
url_hash = hashlib.md5(remote_url.encode()).hexdigest()[:12]
# Include the branch in the hash so different branches of the same repo
# get separate clone directories and cannot race each other.
branch_segment = branch or "default"
url_hash = hashlib.md5(
f"{remote_url}:{branch_segment}".encode()
).hexdigest()[:12]
repo_name = remote_url.split("/")[-1].replace(".git", "") or "repo"
clone_dir = os.path.join(clone_parent, "git-mounts", f"{repo_name}-{url_hash}")
repo_path = os.path.join(clone_dir, "repo-clone")
@@ -303,8 +308,20 @@ def resolve_git_mount_mappings(
# Single match: mount directly to target_path
mount_target = final_target
else:
# Multiple matches: append relative path to target
rel_path = os.path.relpath(matched_path, repo_path)
# Multiple matches: append the path relative to the glob's base
# directory so `packages/*` → `/app/packages` yields
# `/app/packages/api` instead of `/app/packages/packages/api`.
first_glob_idx = min(
(source_path.find(c) for c in "*?[" if c in source_path),
default=len(source_path),
)
base_relative = os.path.dirname(source_path[: first_glob_idx + 1])
base_full = (
os.path.join(repo_path, base_relative)
if base_relative
else repo_path
)
rel_path = os.path.relpath(matched_path, base_full)
mount_target = os.path.join(final_target, rel_path)
volume_mounts.append(
@@ -357,7 +374,14 @@ async def resolve_single_git_mount(
repo_path = await asyncio.to_thread(
clone_git_repo, remote_url, branch, instance_dir
)
except Exception:
except Exception as exc:
logger.warning(
"Git mount clone failed for %s (branch=%s): %s",
remote_url,
branch,
exc,
exc_info=True,
)
return []
# Resolve all mappings from the cloned repo
@@ -1241,7 +1265,7 @@ async def start_tool_instance(
# Fetch tool type early to determine home directory and container user
tool_type = await session.get(ToolType, instance.tool_type_id)
home_dir = "/root"
home_dir = tool_type.home_directory if tool_type and tool_type.home_directory else "/root"
container_uid = 0
container_gid = 0
if tool_type and tool_type.definition_type == "manifest" and tool_type.manifest_id:
@@ -1259,16 +1283,22 @@ async def start_tool_instance(
deep_merge(dict(base_def.manifest), manifest)
)
home_dir = get_manifest_home_dir(manifest)
runtime = manifest.get("runtime", {})
if runtime.get("working_dir"):
working_directory = expand_container_path(
runtime["working_dir"], home_dir
)
user_cfg = manifest.get("user")
if user_cfg:
container_uid = user_cfg.get("uid", 0)
container_gid = user_cfg.get("gid", 0)
logger.debug(
"Manifest user resolved for instance %s: uid=%s, gid=%s, home=%s",
"Manifest user resolved for instance %s: uid=%s, gid=%s, home=%s, working_directory=%s",
instance.id,
container_uid,
container_gid,
home_dir,
working_directory,
)
# Apply selected config profile if any
@@ -1281,6 +1311,16 @@ async def start_tool_instance(
profile_env, profile_files, profile_mounts, profile_hints = (
apply_resolved_profile(instance_dir, resolved, home_dir)
)
# Profile hints override the manifest/tool defaults, and git mounts
# need the final working directory to resolve relative target paths.
if profile_hints.get("start_command"):
start_command = profile_hints["start_command"]
if profile_hints.get("working_directory"):
working_directory = expand_container_path(
profile_hints["working_directory"], home_dir
)
if profile_hints.get("port_override"):
port_override = profile_hints["port_override"]
env_vars.update(profile_env)
config_files.update(profile_files)
extra_volumes.extend(profile_mounts)
@@ -1288,12 +1328,6 @@ async def start_tool_instance(
session, resolved, instance_dir, working_directory, home_dir
)
extra_volumes.extend(git_mount_volumes)
if profile_hints.get("start_command"):
start_command = profile_hints["start_command"]
if profile_hints.get("working_directory"):
working_directory = profile_hints["working_directory"]
if profile_hints.get("port_override"):
port_override = profile_hints["port_override"]
logger.debug(
"Applied config profile %s to instance %s (env=%d, files=%d, mounts=%d, git_mounts=%d)",
resolved.profile_name,