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
- Use workspace-relative paths in file tool calls
- Derive relative paths from absolute ones with os.path.relpath before calling
- 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
- Use the task tools (get/list) rather than raw file reads of task_<id>.json
- Keep scratch files inside the workspace and reference them relatively
- Launch the harness from the workspace root
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
- 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/ad9050c8b4783eb8.
Report an issue: GitHub.