fix: dedupe local targets so a dir is never both copied and mounted

If the same directory is passed via --target and --mount (or as duplicate
values), it previously produced two targets — copied AND bind-mounted, and
the copied one could trip the size pre-flight. Dedupe by resolved path,
preferring the bind mount.
This commit is contained in:
Mads Hvelplund 2026-06-19 06:55:02 +02:00
parent 8e30ad68c9
commit cd4b6c7a68
No known key found for this signature in database
GPG key ID: 08208EA18E154F00
3 changed files with 60 additions and 0 deletions

View file

@ -37,6 +37,7 @@ from strix.interface.utils import (
check_docker_connection,
clone_repository,
collect_local_sources,
dedupe_local_targets,
find_oversized_local_targets,
generate_run_name,
image_exists,
@ -507,6 +508,8 @@ Examples:
except ValueError as e:
parser.error(str(e))
args.targets_info = dedupe_local_targets(args.targets_info)
assign_workspace_subdirs(args.targets_info)
rewrite_localhost_targets(args.targets_info, HOST_GATEWAY_HOSTNAME)

View file

@ -1290,6 +1290,31 @@ def build_mount_targets_info(mount_paths: list[str]) -> list[dict[str, Any]]:
return targets_info
def dedupe_local_targets(targets_info: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Collapse local_code targets that resolve to the same path.
When a directory is supplied both as a copied ``--target`` and via
``--mount`` (or as duplicate values of either), keep one entry and prefer
the bind-mounted one — so the same tree is never both streamed in and
mounted. Order is preserved; non-local targets pass through untouched.
"""
result: list[dict[str, Any]] = []
index_by_path: dict[str, int] = {}
for target in targets_info:
details = target.get("details") or {}
path = details.get("target_path")
if target.get("type") != "local_code" or not path:
result.append(target)
continue
existing = index_by_path.get(path)
if existing is None:
index_by_path[path] = len(result)
result.append(target)
elif details.get("mount") and not (result[existing].get("details") or {}).get("mount"):
result[existing] = target # bind mount supersedes the copied entry
return result
def _is_localhost_host(host: str) -> bool:
host_lower = host.lower().strip("[]")

View file

@ -13,6 +13,7 @@ if TYPE_CHECKING:
from strix.interface.utils import (
build_mount_targets_info,
collect_local_sources,
dedupe_local_targets,
directory_size_bytes,
find_oversized_local_targets,
)
@ -122,3 +123,34 @@ def test_build_mount_targets_info_rejects_empty_path(empty: str) -> None:
# and silently bind-mount it into the sandbox.
with pytest.raises(ValueError, match="must not be empty"):
build_mount_targets_info([empty])
def test_dedupe_keeps_distinct_targets_in_order() -> None:
targets = [
_local_target("/a"),
{"type": "web_application", "details": {"target_url": "https://x"}},
_local_target("/b", mount=True),
]
assert dedupe_local_targets(targets) == targets
def test_dedupe_mount_supersedes_copied_same_path() -> None:
copied = _local_target("/repo")
mounted = _local_target("/repo", mount=True)
# Copied first, then mounted: the single surviving entry is the mount.
result = dedupe_local_targets([copied, mounted])
assert len(result) == 1
assert result[0]["details"]["mount"] is True
# Order-independent: mounted first, copied second also yields the mount.
result_rev = dedupe_local_targets([mounted, copied])
assert len(result_rev) == 1
assert result_rev[0]["details"]["mount"] is True
def test_dedupe_collapses_duplicate_mounts() -> None:
result = dedupe_local_targets(
[_local_target("/repo", mount=True), _local_target("/repo", mount=True)]
)
assert len(result) == 1