langchain-ai/deepagents · error · PermissionError

Skill path {skill_path} resolves outside all allowed skill d

Error message

Skill path {skill_path} resolves outside all allowed skill directories. If this is a symlink, add the target directory to {EXTRA_SKILLS_DIRS} or [skills].extra_allowed_dirs in {config_file}.

What it means

`load_skill_content` resolves the skill path and checks it against the allowlist of permitted skill roots before reading. If the resolved path escapes all allowed directories (commonly via a symlink), it raises `PermissionError` explaining how to allowlist the target directory via `EXTRA_SKILLS_DIRS` or `[skills].extra_allowed_dirs`. This is a fail-closed containment guard on skill file reads.

Source

Thrown at libs/code/deepagents_code/skills/load.py:215

    from pathlib import Path

    path = Path(skill_path).resolve()

    if allowed_roots and not any(path.is_relative_to(root) for root in allowed_roots):
        logger.warning(
            "Skill path %s is outside all allowed roots, refusing to read",
            skill_path,
        )
        from deepagents_code._env_vars import EXTRA_SKILLS_DIRS
        from deepagents_code._paths import PATHS

        msg = (
            f"Skill path {skill_path} resolves outside all allowed skill "
            "directories. If this is a symlink, add the target directory to "
            f"{EXTRA_SKILLS_DIRS} or [skills].extra_allowed_dirs "
            f"in {PATHS.display(PATHS.profile.config_file)}."
        )
        raise PermissionError(msg)

    try:
        return path.read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError):
        logger.warning(
            "Could not read skill content from %s", skill_path, exc_info=True
        )
        return None

View on GitHub (pinned to a1af029e6e)

Solutions

  1. If it is a symlink, add the target directory to the [skills].extra_allowed_dirs list in your config file or the EXTRA_SKILLS_DIRS setting
  2. Move or copy the skill physically into an allowed skills directory
  3. Confirm with `readlink -f <skill_path>` where the path actually resolves
  4. Then re-run the command; trust the directory if prompted

Example fix

// before (toml)
# [skills].extra_allowed_dirs not set; ~/my-skills symlinked into skills dir
// after (config.toml)
[skills]
extra_allowed_dirs = ["/home/me/my-skills"]
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def resolves_within(skill_path: Path, allowed_roots: list[Path]) -> bool:
    resolved = skill_path.resolve()
    return any(root.resolve() in resolved.parents or resolved == root.resolve()
               for root in allowed_roots)

Type guard

def is_contained(skill_path: Path, allowed_roots: list[Path]) -> bool:
    resolved = skill_path.resolve()
    return any(resolved.is_relative_to(root.resolve()) for root in allowed_roots)

Try / catch

try:
    content = load_skill_content(skill_path)
except PermissionError as exc:
    print(f'add target dir to extra_allowed_dirs: {exc}')
    raise SystemExit(1) from exc

Prevention

When it happens

Trigger: `load_skill_content(skill_path)` (via `_load`, `_retry`, or `run_non_interactive`) when the resolved absolute path is outside every allowed skill root — including the symlink-escape case where the visible path is inside a root but its target is not.

Common situations: Skills directory symlinked to a repo elsewhere on disk; SKILL.md containing links resolved outside roots; project skills placed outside the configured project/user skills directories.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/1dd09076191262dc. Report an issue: GitHub.