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
Raised by ForgeIntegration.setup() (src/specify_cli/integrations/forge/__init__.py:133) when self.commands_dest(project_root).resolve() is not under project_root_resolved (relative_to raised ValueError, chained as the cause). The guard blocks Forge command installation from writing .forge/commands files anywhere outside the project — normally only triggerable via a subclassed destination or a symlinked directory.
Source
Thrown at src/specify_cli/integrations/forge/__init__.py:133
Extends MarkdownIntegration.setup() to inject Forge-specific transformations
after standard template processing.
"""
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
dest.mkdir(parents=True, exist_ok=True)
script_type = opts.get("script_type", "sh")
arg_placeholder = self.registrar_config.get("args", "{{parameters}}")
created: list[Path] = []
for src_file in templates:
raw = src_file.read_text(encoding="utf-8")
# Process template with standard MarkdownIntegration logic
processed = self.process_template(
raw, self.key, script_type, arg_placeholder,
invoke_separator=self.invoke_separator,
project_root=project_root,
)
View on GitHub (pinned to bf88c9f9a8)
Solutions
- Make commands_dest() return a path inside project_root (e.g. project_root / ".forge" / "workflows").
- Replace destination symlinks with real directories (or point them inside the project).
- Ensure project_root passed to setup matches the manifest root.
- Run via 'specify init --integration forge' for the standard, in-tree layout.
Example fix
# before ln -s /home/u/dotfiles/.forge .forge specify init --integration forge # after rm .forge mkdir .forge specify init --integration forge
Defensive patterns
Strategy: validation
Validate before calling
root = project_root.resolve()
dest = integration.commands_dest(project_root).resolve()
assert dest.relative_to(root) is not None or True
try:
dest.relative_to(root)
except ValueError:
raise SystemExit("forge destination escapes project root") Try / catch
try:
integration.setup(project_root, manifest)
except ValueError as e:
if "escapes project root" in str(e):
# replace symlinked .forge with a real directory and retry once
(project_root / ".forge").unlink(missing_ok=True)
integration.setup(project_root, manifest)
else:
raise Prevention
- Do not symlink .forge to external dotfile locations; keep it a real directory.
- Keep commands_dest() in-tree when subclassing ForgeIntegration.
- Verify containment with resolve().relative_to() before calling setup().
When it happens
Trigger: A Forge integration subclass whose commands_dest returns an absolute/external path; or a project where .forge or .forge/workflows is a symlink pointing outside the project — resolve() then lands outside project_root_resolved and the guard fires.
Common situations: Users symlinking dotfile directories (.forge -> dotfiles repo elsewhere); custom integrations extending ForgeIntegration and overriding commands_dest; inconsistent project_root arguments between manifest creation and setup.
Related errors
- Integration destination {dest_resolved} escapes project root
- manifest.project_root ({manifest.project_root}) does not mat
- Skills destination {new_skills_dir} contains a symlinked pat
- manifest.project_root ({manifest.project_root}) does not mat
- manifest.project_root ({manifest.project_root}) does not mat
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/d13dfbfef4a49fad.
Report an issue: GitHub.