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

Path escapes workspace: {p}

Error message

Path escapes workspace: {p}

What it means

The same workspace sandbox guard as s02, duplicated at agents/s03_todo_write.py:96 in the todo-list agent script. Every file tool path is joined onto WORKDIR (Path.cwd() at launch), resolved, and must remain inside WORKDIR. Absolute paths win over the join in pathlib, and resolve() collapses `..` and follows symlinks, so both escape routes are caught.

Source

Thrown at agents/s03_todo_write.py:96

        if not self.items:
            return "No todos."
        lines = []
        for item in self.items:
            marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}[item["status"]]
            lines.append(f"{marker} #{item['id']}: {item['text']}")
        done = sum(1 for t in self.items if t["status"] == "completed")
        lines.append(f"\n({done}/{len(self.items)} completed)")
        return "\n".join(lines)


TODO = TodoManager()


# -- Tool implementations --
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. Pass workspace-relative paths ("notes.md", "src/app.py") to file tools
  2. Convert known-in-workspace absolute paths with os.path.relpath before calling
  3. State in the system prompt / todo text that tool paths are relative to WORKDIR
  4. Launch the agent with cwd = the workspace so WORKDIR is correct

Example fix

// before
{"op": "write", "path": "/tmp/errlookup-AdzUmp/TODO.md"}
// ValueError: Path escapes workspace

// after
{"op": "write", "path": "TODO.md"}
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def to_workspace_relative(p: str, workdir: Path) -> str | None:
    """Return a safe relative path, or None if it escapes."""
    try:
        abs_p = (workdir / p).resolve()
        return os.path.relpath(abs_p, workdir) if abs_p.is_relative_to(workdir) else None
    except (OSError, ValueError, RuntimeError):
        return None

rel = to_workspace_relative(model_path, WORKDIR)
assert rel and not rel.startswith(".."), "path escapes workspace"

Type guard

def is_safe_tool_path(p: object, workdir) -> bool:
    if not isinstance(p, str) or not p or p.startswith(("/", "~")):
        return False
    try:
        return (workdir / p).resolve().is_relative_to(workdir)
    except (OSError, RuntimeError):
        return False

Try / catch

try:
    path = safe_path(p)
except ValueError:
    return f"Path rejected: {p}. Use a path relative to {WORKDIR}."

Prevention

When it happens

Trigger: The model calls a file tool with "/abs/path", "../../../outside.txt", or a path through a symlink pointing outside the workspace. Also fires when the harness was launched from a cwd unrelated to the files the model was told (in its system prompt) it owns.

Common situations: Model copies an absolute path from bash tool output or from the system prompt's stated directory. Harness started from the wrong directory. Symlinked workspace roots resolving outside the literal cwd.

Related errors


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