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

  1. Set config['folder'] to a project-relative directory name such as '.myagent/' — never absolute, never '..'-containing.
  2. Remove or replace destination symlinks that point outside the project with real directories.
  3. 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

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


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/9a56bf5ff0a3cb2b. Report an issue: GitHub.