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

Path escapes workspace: {p}

Error message

Path escapes workspace: {p}

What it means

Same safe_path() containment guard as in the other session modules, duplicated into the consolidated agent s_full.py. It resolves WORKDIR-joined paths and rejects anything landing outside WORKDIR, protecting the file tools from path traversal. Because it appears in the merged module, every file tool in the full agent (read, write, edit, etc.) inherits the same restriction.

Source

Thrown at agents/s_full.py:77

TEAM_DIR = WORKDIR / ".team"
INBOX_DIR = TEAM_DIR / "inbox"
TASKS_DIR = WORKDIR / ".tasks"
SKILLS_DIR = WORKDIR / "skills"
TRANSCRIPT_DIR = WORKDIR / ".transcripts"
TOKEN_THRESHOLD = 100000
POLL_INTERVAL = 5
IDLE_TIMEOUT = 60

VALID_MSG_TYPES = {"message", "broadcast", "shutdown_request",
                   "shutdown_response", "plan_approval_response"}


# === SECTION: base_tools ===
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)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Always pass paths relative to WORKDIR and without leading '..' segments
  2. Normalize absolute in-workspace paths with Path(p).relative_to(WORKDIR) before the call
  3. Audit symlinks inside the workspace with find WORKDIR -type l and remove ones pointing outside
  4. Verify the WORKDIR value used at agent construction matches where files actually live

Example fix

// before
run_edit("/home/user/proj/src/app.py", ...)
// after
run_edit("src/app.py", ...)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def workspace_relative(p: str) -> str | None:
    ap = Path(p)
    if not ap.is_absolute():
        ap = (WORKDIR / p)
    try:
        return str(ap.resolve().relative_to(WORKDIR.resolve()))
    except ValueError:
        return None  # escapes workspace — do not call the tool

if (rel := workspace_relative(p)) is not None:
    run_read(rel)

Prevention

When it happens

Trigger: A tool call with "../secrets.env", an absolute path from a prior error message, or a workspace symlink chain escaping WORKDIR after resolve(). Any file tool in s_full.py (run_read/run_write/run_edit) receives the path.

Common situations: Agent copies an absolute path out of a traceback or git output and feeds it back to a file tool; nested symlinked node_modules-style layouts resolving outside the tree; WORKDIR misconfigured at agent startup.

Related errors


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