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/s05_skill_loading.py:121 in the skill-loading agent. All file tool calls pass through safe_path(), which resolves the model's path against WORKDIR and rejects anything that escapes it. This variant coexists with load_skill, so failures usually involve paths the model picked up from skill bodies rather than the skill mechanism itself.

Source

Thrown at agents/s05_skill_loading.py:121

            return f"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}"
        return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"


SKILL_LOADER = SkillLoader(SKILLS_DIR)

# Layer 1: skill metadata injected into system prompt
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Use load_skill to access specialized knowledge before tackling unfamiliar topics.

Skills available:
{SKILL_LOADER.get_descriptions()}"""


# -- 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. Keep paths in file tool calls relative to WORKDIR; treat skill-internal paths as data to rewrite, not to follow blindly
  2. Author skills with workspace-relative paths or placeholders
  3. Use load_skill (not raw file reads) to access skill content; if skill files are outside WORKDIR, that is by design

Example fix

# before
read_file("/home/beagle/.agents/skills/agnt/SKILL.md")
# ValueError: Path escapes workspace

# after
load_skill("agnt")  # content is returned by the skill loader
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

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

if not is_skill_safe_path(p, WORKDIR):
    p = None  # do not call the file tool; use load_skill content instead

Type guard

def is_workspace_path_str(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} is outside the workspace. Use load_skill for skill content and relative paths for files."

Prevention

When it happens

Trigger: A loaded skill's instructions reference files by absolute path or `../` and the model copies them into a file tool call. Or the model tries to read the skill files themselves from outside the workspace (SKILL_LOADER may live elsewhere). Absolute paths and symlink escapes resolve outside WORKDIR and are rejected.

Common situations: Skill markdown containing machine-specific absolute paths from the skill author's machine. Model attempting to inspect the skills directory directly instead of via load_skill. Harness cwd mismatch.

Related errors


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