github/spec-kit · error · ValueError
Integration destination {dest} escapes project root {project
Error message
Integration destination {dest} escapes project root {project_root_resolved} What it means
During command installation, the base-class setup() resolves the commands destination (config folder + commands_subdir) and requires it to sit inside the resolved project root. This guard blocks a registrar_config['dir'] (or a symlinked destination) that would write command files outside the user's project — for example an absolute folder like '/etc' or a '../' escape. This instance is the guard in the plain copy path used by non-template integrations.
Source
Thrown at src/specify_cli/integrations/base.py:904
and call ``process_template()`` in their own loop — see
``CopilotIntegration`` for an example.
"""
templates = self.list_command_templates()
if not templates:
return []
project_root_resolved = project_root.resolve()
if manifest.project_root != project_root_resolved:
raise ValueError(
f"manifest.project_root ({manifest.project_root}) does not match "
f"project_root ({project_root_resolved})"
)
dest = self.commands_dest(project_root).resolve()
try:
dest.relative_to(project_root_resolved)
except ValueError as exc:
raise ValueError(
f"Integration destination {dest} escapes "
f"project root {project_root_resolved}"
) from exc
created: list[Path] = []
for src_file in templates:
dst_name = self.command_filename(src_file.stem)
dst_file = self.copy_command_to_directory(src_file, dest, dst_name)
self.record_file_in_manifest(dst_file, project_root, manifest)
created.append(dst_file)
return created
def teardown(
self,
project_root: Path,View on GitHub (pinned to bf88c9f9a8)
Solutions
- Set config['folder'] to a project-relative directory name such as '.myagent/' — never absolute, never '..'-containing.
- Remove or replace destination symlinks that point outside the project with real directories.
- Verify with: `python -c "from pathlib import Path; print((Path('.myagent').resolve()))"` from the project root and confirm it stays inside.
Example fix
# before
class MyIntegration(IntegrationBase):
config = {"folder": "/etc/my-agent", ...}
# after
class MyIntegration(IntegrationBase):
config = {"folder": ".my-agent", ...} Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def dest_inside_project(project_root: Path, integration) -> bool:
root = project_root.resolve()
try:
integration.commands_dest(project_root).resolve().relative_to(root)
except ValueError:
return False
return True Prevention
- Keep config['folder'] a bare relative directory name like '.myagent/'.
- Never hard-code absolute paths or '..' in integration folder/dir config.
- Scan the project for out-of-tree symlinks before running integration setup.
When it happens
Trigger: An integration subclass whose config['folder'] is an absolute path or contains '..' so commands_dest(project_root).resolve() falls outside project_root.resolve(); or the destination directory being a symlink pointing outside the project.
Common situations: Hand-written custom integrations with folder values like '~/.agent' or '/opt/commands'; symlinked dot-directories (e.g. .kilo -> elsewhere) in the user's project; copy-pasting an integration class and forgetting to change folder to a project-relative path.
Related errors
- Skills destination {skills_dir} escapes project root {projec
- Error: --short-name requires a value
- Error: --number must be a non-negative integer
- Error: branch_template must not place {slug} before {number}
- Error: branch_template must put {number}- at the start of th
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/9a56bf5ff0a3cb2b.
Report an issue: GitHub.