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/s06_context_compact.py:138 in the context-compaction agent. After compaction the transcript is written to a file and only its path plus a summary remain in history; every file tool path (including transcript references) is still checked by safe_path() against WORKDIR.
Source
Thrown at agents/s06_context_compact.py:138
"1) What was accomplished, 2) Current state, 3) Key decisions made. "
"Be concise but preserve critical details."
f"{focus_instruction}\n\n" + conversation_text}],
max_tokens=2000,
)
summary = next((block.text for block in response.content if hasattr(block, "text")), "")
if not summary:
summary = "No summary generated."
# Replace all messages with compressed summary
return [
{"role": "user", "content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}"},
]
# -- 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
- Reference the transcript by its workspace-relative path; derive it from the absolute one via os.path.relpath
- Keep all file tool arguments relative to WORKDIR
- Ensure the harness is launched from the intended workspace directory
Example fix
# before
read_file("/tmp/errlookup-AdzUmp/transcripts/session_3.jsonl")
# ValueError: Path escapes workspace
# after
read_file("transcripts/session_3.jsonl") Defensive patterns
Strategy: validation
Validate before calling
import os
from pathlib import Path
def transcript_rel(abs_transcript: str, workdir: Path) -> str | None:
try:
ap = Path(abs_transcript).resolve()
return os.path.relpath(ap, workdir) if ap.is_relative_to(workdir) else None
except (OSError, ValueError):
return None
rel = transcript_rel(msg_transcript_path, WORKDIR)
assert rel, "transcript outside workspace; do not re-read it with file tools" Type guard
def is_post_compaction_safe_path(p: object, workdir) -> bool:
if not isinstance(p, str) or not p:
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"{p} escapes the workspace. Convert to a path relative to {WORKDIR} (e.g. transcripts/session_3.jsonl)." Prevention
- Write transcripts inside the workspace so post-compaction reads stay relative
- Derive relative paths from the transcript path embedded in the compaction message
- Keep file tool arguments relative even after context resets — the sandbox survives compaction
When it happens
Trigger: The model calls a file tool with an absolute transcript path (the compaction message embeds an absolute transcript_path) or an outside/`../` path, and safe_path() rejects it. Also triggered by cwd/workspace mismatch at launch.
Common situations: Post-compaction turns where the model tries to re-read the transcript using the absolute path from the "[Conversation compressed. Transcript: ...]" message instead of a relative one. Paths copied from earlier tool output that was itself produced before a directory change.
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/47c04cd51f30d796.
Report an issue: GitHub.