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

Path escapes workspace: {p}

Error message

Path escapes workspace: {p}

What it means

The workspace sandbox guard at agents/s07_task_system.py:128 in the task-system agent, applied to its base file tools. Paths are joined onto WORKDIR (= Path.cwd() at launch), resolved, and must remain under WORKDIR. Task JSON files live in TASKS_DIR and are managed by TaskManager directly, so this guard covers the model-facing file tools, not task files.

Source

Thrown at agents/s07_task_system.py:128

            tasks.append(json.loads(f.read_text()))
        if not tasks:
            return "No tasks."
        lines = []
        for t in tasks:
            marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]")
            blocked = f" (blocked by: {t['blockedBy']})" if t.get("blockedBy") else ""
            lines.append(f"{marker} #{t['id']}: {t['subject']}{blocked}")
        return "\n".join(lines)


TASKS = TaskManager(TASKS_DIR)


# -- Base 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. Use workspace-relative paths in file tool calls
  2. Derive relative paths from absolute ones with os.path.relpath before calling
  3. Launch the agent from the workspace root

Example fix

// before
{"op": "read", "path": "/tmp/errlookup-AdzUmp/tasks/task_1.json"}
// ValueError: Path escapes workspace

// after
{"op": "read", "path": "tasks/task_1.json"}  // or use task_get(1)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def is_task_file_readable_rel(p: str, workdir: Path) -> bool:
    try:
        return (workdir / p).resolve().is_relative_to(workdir)
    except (OSError, RuntimeError):
        return False

# prefer structured access over raw reads of task files:
# task_get(1) instead of read_file("tasks/task_1.json") with an absolute path

Type guard

def is_workspace_relative(p: object) -> bool:
    return isinstance(p, str) and bool(p) and not p.startswith(("/", "~")) and ".." not in Path(p).parts

Try / catch

try:
    path = safe_path(p)
except ValueError:
    return f"{p} escapes the workspace. Use task_get/task_list for task data, or a relative path for files."

Prevention

When it happens

Trigger: A file tool call with an absolute path (including to the tasks dir itself), a `../` traversal, or a symlink target outside the workspace. Also when the harness cwd differs from the directory holding the files the model manipulates.

Common situations: Model reading/writing task scratch files by absolute path taken from task JSON output. Launching the harness from $HOME. Symlinked workspaces.

Related errors


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