shareAI-lab/learn-claude-code · error · ValueError

Path escapes workspace: {p}

Error message

Path escapes workspace: {p}

What it means

Raised by safe_path() in the s12 agent tool layer when a user-supplied relative path resolves outside WORKDIR. The function joins the path onto WORKDIR, resolves symlinks and .. segments, then requires the result to be inside WORKDIR — a classic sandbox containment check that blocks path traversal. All file tools (read/write/edit) route through it, so any traversal attempt fails before touching the filesystem.

Source

Thrown at agents/s12_worktree_task_isolation.py:481

            "worktree.keep",
            task={"id": wt.get("task_id")} if wt.get("task_id") is not None else {},
            worktree={
                "name": name,
                "path": wt.get("path"),
                "status": "kept",
            },
        )
        return json.dumps(kept, indent=2) if kept else f"Error: Unknown worktree '{name}'"


WORKTREES = WorktreeManager(REPO_ROOT, TASKS, EVENTS)


# -- Base tools (kept minimal, same style as previous sessions) --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(
            command,
            shell=True,
            cwd=WORKDIR,
            capture_output=True,
            text=True,
            timeout=120,
        )
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Strip any leading '/' and re-express the path relative to WORKDIR before calling the tool
  2. Convert absolute paths under WORKDIR to relative: path.relative_to(WORKDIR) when possible
  3. If a legitimate symlink inside WORKDIR points outside, restructure so the data lives inside the workspace
  4. If WORKDIR is itself a symlink, pass the resolved real path when constructing the agent

Example fix

// before
run_read("/tmp/errlookup-AdzUmp/notes.txt")  # absolute -> escapes
// after
from pathlib import Path
rel = Path("/tmp/errlookup-AdzUmp/notes.txt").relative_to(WORKDIR)
run_read(str(rel))
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def in_workspace(p: str) -> bool:
    try:
        (WORKDIR / p).resolve().relative_to(WORKDIR.resolve())
        return True
    except ValueError:
        return False

# or normalize absolute paths first:
def to_relative(p: str) -> str:
    ap = Path(p).resolve()
    return str(ap.relative_to(WORKDIR.resolve()))

Try / catch

try:
    safe_path(p)
except ValueError:
    p = str(Path(p).resolve().relative_to(WORKDIR.resolve()))  # only if truly inside
    safe_path(p)

Prevention

When it happens

Trigger: Passing "../../etc/passwd", an absolute path like "/etc/hosts" (WORKDIR / '/etc/hosts' yields /etc/hosts, outside WORKDIR), or a path that traverses a symlink pointing outside WORKDIR. Also fires on benign-looking inputs when WORKDIR itself is a symlink that resolves elsewhere.

Common situations: Agents emitting absolute paths from tool output (e.g. copying a path from an error message or git output); symlinks inside the workspace pointing to system directories; WORKDIR configured as a symlinked path so resolve() moves the root out from under the containment check.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/422534c8936ad761. Report an issue: GitHub.