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
- Pass workspace-relative paths ("notes.md", "src/app.py") to file tools
- Convert known-in-workspace absolute paths with os.path.relpath before calling
- State in the system prompt / todo text that tool paths are relative to WORKDIR
- 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
- Mention todo files with workspace-relative paths in plans and prompts
- Normalize absolute paths to relative before file tool calls
- Launch from the workspace root so relative and absolute views agree
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
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
- Path escapes workspace: {p}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/967302e184729539.
Report an issue: GitHub.