abhigyanpatwari/GitNexus · error · SandboxError
target-controlled {name} must not be a directory
Error message
target-controlled {name} must not be a directory What it means
Precondition guard in _replace_control_file. Before overwriting .gitnexusrc or .gitnexusignore to neutralize target index inputs, the helper lstat's the path; if it currently exists as a directory, the harness refuses (it cannot atomically replace a directory with a regular file via O_CREAT|O_EXCL). This protects the neutralization step from an inconsistent starting state.
Source
Thrown at eval/workflow_bench/sanitized_graph.py:114
raise SandboxError("sandbox_dependencies must be a list")
for item in dependencies:
if not isinstance(item, Mapping):
continue
for field in ("source", "target"):
value = item.get(field)
if isinstance(value, str) and _is_restricted_path(value):
raise SandboxError(f"sandbox dependency cannot expose prebuilt graph or harness data: {value}")
def _replace_control_file(root: Path, name: str, payload: bytes) -> None:
path = root / name
try:
metadata = path.lstat()
except FileNotFoundError:
metadata = None
if metadata is not None:
if stat.S_ISDIR(metadata.st_mode):
raise SandboxError(f"target-controlled {name} must not be a directory")
path.unlink()
descriptor = os.open(
path,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
0o600,
)
try:
view = memoryview(payload)
while view:
written = os.write(descriptor, view)
if written <= 0:
raise OSError(f"short write while neutralizing {name}")
view = view[written:]
os.fsync(descriptor)
finally:
os.close(descriptor)
View on GitHub (pinned to d540b00184)
Solutions
- Inspect the clone root: 'ls -la <clone>/.gitnexusrc <clone>/.gitnexusignore' and confirm they are not directories.
- Remove or convert the directory to a regular file in the task fixture so the harness can replace it.
- Re-create the sanitized task snapshot after fixing the fixture so the bad state does not recur.
- Check that no sandbox_copy or sandbox_dependencies entry creates one of these names as a directory.
Example fix
# before (fixture builds .gitnexusrc as a dir)
mkdir .gitnexusrc
# after
echo '{}' > .gitnexusrc Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
import stat
clone = Path("clone-root")
for name in (".gitnexusrc", ".gitnexusignore"):
p = clone / name
if p.is_symlink():
raise SystemExit(f"{name} is a symlink")
if p.exists() and not p.is_file():
raise SystemExit(f"{name} must be a regular file, not a directory") Type guard
from pathlib import Path
import stat
def control_file_is_writable(path: Path) -> bool:
try:
mode = path.lstat().st_mode
except FileNotFoundError:
return True
return not stat.S_ISDIR(mode) and not stat.S_ISLNK(mode) Try / catch
# _replace_control_file is internal; pre-clean the clone before graph prep:
try:
_neutralize_target_index_inputs(clone_root)
except SandboxError as exc:
if "must not be a directory" in str(exc):
log.error(".gitnexusrc/.gitnexusignore is a dir in the fixture; convert to a file")
raise Prevention
- Never create .gitnexusrc or .gitnexusignore as directories in fixtures.
- Audit sandbox_copy entries that might create those names.
- Recreate the sanitized task snapshot after fixing the fixture.
When it happens
Trigger: _neutralize_target_index_inputs calls _replace_control_file for .gitnexusrc or .gitnexusignore, and one of them is a directory in the sanitized clone root. The error message interpolates the offending control-file name.
Common situations: A task spec or fixture created .gitnexusrc/ as a directory (e.g. to hold snippets); a symlinked config layout resolved to a directory; a previous failed run left a partial directory behind.
Related errors
- Compound Engineering plugin file must be regular and non-sym
- sanitized task identity drifted between graph preparation an
- short write while neutralizing {name}
- target .gitnexus path must be a real directory before graph
- {label} is unavailable: {path}: {exc}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/36ce2fcfa315d4b6.
Report an issue: GitHub.