shareAI-lab/learn-claude-code · error · ValueError
Worktree path escapes directory: {name!r}
Error message
Worktree path escapes directory: {name!r} What it means
_worktree_path() maps a worktree name to WORKTREES_DIR/<name> and refuses the result unless the resolved path stays strictly inside WORKTREES_ROOT and is not the root itself. This blocks traversal via names that survive _validate_worktree_name (e.g. names containing separators after odd normalization) and symlinked worktrees pointing elsewhere.
Source
Thrown at s15_integrated_harness/code.py:391
WORKTREES_ROOT = WORKTREES_DIR.resolve()
VALID_WORKTREE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
def validate_worktree_name(name: str) -> str | None:
if not isinstance(name, str) or not VALID_WORKTREE_NAME.fullmatch(name):
return ("worktree name must be 1-64 letters, digits, dots, "
"underscores, or dashes, and start with a letter or digit")
if name in {".", ".."} or ".." in name:
return "worktree name cannot contain '..'"
return None
def _worktree_path(name: str) -> Path:
path = (WORKTREES_DIR / name).resolve()
if (not WORKTREES_ROOT.is_relative_to(WORKDIR.resolve())
or not path.is_relative_to(WORKTREES_ROOT)
or path == WORKTREES_ROOT):
raise ValueError(f"Worktree path escapes directory: {name!r}")
return path
def _worktree_branch(name: str) -> str:
return f"wt/{name}"
def _run_git(args: list[str], cwd: Path | None = None) -> tuple[bool, str]:
"""Run Git without shell interpolation and return (ok, combined output)."""
try:
result = subprocess.run(
["git", *args], cwd=cwd or WORKDIR,
capture_output=True, text=True, timeout=30,
)
except (OSError, subprocess.TimeoutExpired) as exc:
return False, f"{type(exc).__name__}: {exc}"
output = (result.stdout + result.stderr).strip()
return result.returncode == 0, output or "(no output)"View on GitHub (pinned to 985456f4ad)
Solutions
- Pass only a bare name matching the validator (1-64 chars of letters/digits/dots/underscores/dashes, no '..', leading alnum).
- Remove symlinks inside the worktrees directory; worktrees must be real git worktrees created by the harness.
- Keep WORKTREES_DIR physically under WORKDIR.
Example fix
// before
worktree_cwd_for("../shared-build")
// after
worktree_cwd_for("shared-build") // bare name; harness resolves it under WORKTREES_DIR Defensive patterns
Strategy: validation
Validate before calling
import re
WORKTREE_NAME_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}")
def is_valid_worktree_name(name: str) -> bool:
return (
isinstance(name, str)
and bool(WORKTREE_NAME_RE.fullmatch(name))
and name not in {".", ".."}
and ".." not in name
) Type guard
def is_bare_worktree_name(name) -> bool:
return isinstance(name, str) and "/" not in name and "\\" not in name and name not in {".", ".."} and ".." not in name Try / catch
try:
path = _worktree_path(name)
except ValueError as e:
if "escapes directory" in str(e):
return f"Error: invalid worktree name {name!r}"
raise Prevention
- Pass bare names, never paths.
- Reject names containing separators or '..' caller-side.
- Keep WORKTREES_DIR free of symlinks.
When it happens
Trigger: Passing a worktree name that, once joined and resolved, lands outside the worktrees root — e.g. an absolute path (the join semantics can flip), a name whose target inside WORKTREES_DIR is a symlink to another location, or a name resolving exactly to WORKTREES_ROOT itself.
Common situations: Model tool calls passing a path instead of a bare name; pre-created symlinks inside the worktrees directory; moving WORKTREES_DIR outside WORKDIR in config.
Related errors
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/9a1c2937d21d913e.
Report an issue: GitHub.