Fixes #1673
Four ways a target or the workspace mount could be given a directory that belongs to something else. All come from names derived from the user's target string, so they are one PR.
A name made only of dots
sanitize_name("..") returned "..", and derive_repo_base_name returns it for a target whose last segment is ... That value becomes workspace_subdir, and clone_repository turns it into <tmp>/strix_repos/<run_name>/.. - the shared parent - which it rmtrees before the clone that would fail, so one bad target removes every other run's clone. A dot-only name is a directory, not a name, so sanitize_name now maps it to target, the fallback it already used for empty input. Names that merely contain dots (a..b) are untouched.
Two targets, one directory
assign_workspace_subdirs counted per base name, so repo, repo, repo-2 gave the second and the third the same repo-2: one target would mount, and clone into, the other's directory. Names are now counted until unused, so those three become repo, repo-2, repo-2-2; repeated names without such a clash keep the plain counter (app, app-2).
A repository named api-specs
stage_api_specs() mounts specs at api-specs in the workspace, and a repository of that name took the same subdir. The reserved name is seeded into the used set, and such a repository becomes api-specs-2.
--workspace-mount named like a target
attach_workspace_mount named the mount from its directory on its own, so --workspace-mount ./app next to a repository called app produced two sources with workspace_subdir = "app" - two things mounted at one path. Both places now share unique_workspace_subdir(), and the mount becomes app-2.
Validation
pytest tests/test_target_names.py tests/test_api_spec_targets.py tests/test_go_tui_runtime.py -q -> 66 passed.
| input | before | after |
|---|---|---|
. / .. / ... | the same string | target |
git@host:.. (derived base) | .. | target |
repo, repo, repo-2 (subdirs) | repo, repo-2, repo-2 | repo, repo-2, repo-2-2 |
a repository named api-specs | api-specs | api-specs-2 |
mount ./app + repository app | app, app | app, app-2 |
app, app (subdirs) | app, app-2 | app, app-2 |
ruff check + format clean. Found by reading the name-deriving functions together; the input is the user's own target, not provider traffic.
Activity
7Added an end-to-end assertion next to the unit ones, because a unique name only matters if the task the agent reads carries it: three targets (repo, repo, repo-2) now go through assign_workspace_subdirs into build_root_task, and the test requires the rendered text to name all three paths (/workspace/repo, /workspace/repo-2, /workspace/repo-2-2) and build_scan_targets to return exactly those. Before this branch the third target read /workspace/repo-2 in both places, so the assertion fails on main.
pytest tests/test_target_names.py -q -> 20 passed; ruff clean.
[Medium impact] This PR appears safe to merge.
Summary
sanitize_namenow returnstargetfor dot-only names, preventing derived repository names from pointing at the current or parent directory.targetas their workspace folder.Reviews (2) · Last reviewed commit: "fix(interface): never derive a directory..." · Reviewed by Greptile