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

Path escapes workspace: {p}

Error message

Path escapes workspace: {p}

What it means

safe_path() resolves a user/model-supplied relative path against a base (default WORKDIR) and raises if the resolved path is not inside that base. It is the guardrail that keeps every file-oriented tool from touching anything outside the workspace, including '../' escapes and absolute paths that resolve elsewhere.

Source

Thrown at s13_agent_teams/code.py:612

        "only; it is not a sandbox. Worktree removal stays with the host or "
        "user. After spawning a teammate, end the current turn instead of "
        "polling its status; the runtime will deliver team events and wake the "
        "Lead. React to those events, and shut teammates down when "
        "coordination is complete."
    ),
    "workspace": f"Working directory: {WORKDIR}",
}

SYSTEM = "\n\n".join(PROMPT_SECTIONS.values())


# -- Base Tools --

def safe_path(p: str, cwd: Path | None = None) -> Path:
    base = (cwd or WORKDIR).resolve()
    path = (base / p).resolve()
    if not path.is_relative_to(base):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def run_bash(command: str, cwd: Path | None = None) -> str:
    try:
        result = subprocess.run(
            command,
            shell=True,
            cwd=cwd or WORKDIR,
            capture_output=True,
            text=True,
            timeout=120,
        )
        output = (result.stdout + result.stderr).strip()
        output = output[:50000] if output else "(no output)"
        if result.returncode:
            return f"Error: command exited with status {result.returncode}\n{output}"
        return output

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Pass paths relative to the workspace root and stay inside it.
  2. If a file legitimately lives outside, copy it into the workspace first.
  3. For nested agents, pass the agent's worktree as cwd so its relative paths resolve inside its own scope.
  4. Remove symlinks inside the workspace that point outside, or expect them to be rejected.

Example fix

// before
read_file(safe_path('../../shared/config.yaml'))  // ValueError

// after
run_bash('cp /shared/config.yaml ./config.yaml')
read_file(safe_path('config.yaml'))
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def path_in_workspace(p: str, base: Path | None = None) -> bool:
    base = (base or WORKDIR).resolve()
    return (base / p).resolve().is_relative_to(base)

Try / catch

try:
    resolved = safe_path(user_path)
except ValueError as exc:
    if 'escapes workspace' in str(exc):
        return error_to_model(f'{user_path!r} is outside the workspace; use a relative path')
    raise

Prevention

When it happens

Trigger: safe_path('../outside.txt'), safe_path('/etc/passwd') (absolute path joined then resolved outside base), or a path whose intermediate symlink component points outside the workspace; also passing a path with cwd= pointing to a directory that doesn't contain the result.

Common situations: Model-generated file tool calls referencing parent directories or absolute paths; symlinks inside the repo pointing to dotfiles elsewhere; tests calling tools with repo-external fixtures.

Related errors


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