github/spec-kit · error · ValueError

Integration destination {dest_resolved} escapes project root

Error message

Integration destination {dest_resolved} escapes project root {project_root_resolved}

What it means

Raised by CopilotIntegration._setup_commands() (src/specify_cli/integrations/copilot/__init__.py:511) when the resolved commands destination (self.commands_dest(project_root).resolve()) does not lie under the resolved project root — Path.relative_to(project_root_resolved) raised ValueError, which is chained into this guard. It is a containment check preventing the integration from writing command files outside the project.

Source

Thrown at src/specify_cli/integrations/copilot/__init__.py:511

            raise ValueError(
                f"manifest.project_root ({manifest.project_root}) does not match "
                f"project_root ({project_root_resolved})"
            )

        templates = self.list_command_templates()
        if not templates:
            return []

        from ...presets import PresetResolver

        preset_resolver = PresetResolver(project_root_resolved)

        dest = self.commands_dest(project_root)
        dest_resolved = dest.resolve()
        try:
            dest_resolved.relative_to(project_root_resolved)
        except ValueError as exc:
            raise ValueError(
                f"Integration destination {dest_resolved} escapes "
                f"project root {project_root_resolved}"
            ) from exc
        dest.mkdir(parents=True, exist_ok=True)
        created: list[Path] = []

        script_type = opts.get("script_type", "sh")
        arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS")

        # 1. Process and write command files as .agent.md
        for src_file in templates:
            resolved_template = preset_resolver.resolve(
                f"speckit.{src_file.stem}", template_type="command"
            )
            source_path = resolved_template or src_file
            raw = source_path.read_text(encoding="utf-8")
            processed = self.process_template(
                raw, self.key, script_type, arg_placeholder,

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Ensure the destination returned by commands_dest(project_root) resolves under project_root.resolve().
  2. Remove or repoint any symlinked destination directories (.github, .github/agents) inside the project.
  3. Pass the same resolved project_root used elsewhere in the call.
  4. If you subclassed CopilotIntegration, keep the destination relative and in-tree.

Example fix

# before
class MyCopilot(CopilotIntegration):
    def commands_dest(self, project_root):
        return Path("/opt/shared/agents")  # escapes project root

# after
class MyCopilot(CopilotIntegration):
    def commands_dest(self, project_root):
        return project_root / ".github" / "agents"
Defensive patterns

Strategy: validation

Validate before calling

root = project_root.resolve()
dest = integration.commands_dest(project_root).resolve()
try:
    dest.relative_to(root)
except ValueError:
    raise SystemExit(f"destination {dest} escapes {root}")

Try / catch

try:
    created = integration.setup(project_root, manifest)
except ValueError as e:
    if "escapes project root" in str(e):
        fix_symlinked_dest(project_root / ".github")
    else:
        raise

Prevention

When it happens

Trigger: Calling CopilotIntegration.setup(..., --commands mode) where commands_dest() resolves outside project_root — in practice caused by subclassing/overriding commands_dest(), or a symlinked destination directory pointing elsewhere, since the raw config folder (.github/agents) is normally in-tree.

Common situations: Custom integration subclass overriding commands_dest to an absolute or external path; a project directory containing a symlink like .github -> /somewhere/else; passing an inconsistent project_root that differs from the one used to build the destination.

Related errors


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