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 GenericIntegration.setup() (src/specify_cli/integrations/generic/__init__.py:124) when (project_root / commands_dir).resolve() falls outside project_root_resolved — the relative_to containment check threw and was chained. Because commands_dir is user-supplied, this guard is the critical defense against '../' or absolute values writing outside the project.
Source
Thrown at src/specify_cli/integrations/generic/__init__.py:124
"""Install commands to the user-provided commands directory."""
commands_dir = self._resolve_commands_dir(parsed_options, opts)
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 = (project_root / commands_dir).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 = "$ARGUMENTS"
created: list[Path] = []
for src_file in templates:
raw = src_file.read_text(encoding="utf-8")
processed = self.process_template(
raw, self.key, script_type, arg_placeholder,
project_root=project_root,
)
dst_name = self.command_filename(src_file.stem)
dst_file = self.write_file_and_record(
processed, dest / dst_name, project_root, manifestView on GitHub (pinned to bf88c9f9a8)
Solutions
- Use a relative commands_dir without '..' that stays inside the project (e.g. '.myagent/commands').
- Remove destination symlinks that resolve outside the project.
- If a global install is desired, run specify per-project instead of pointing commands_dir outside.
Example fix
# before specify init --integration generic --integration-options="--commands-dir /usr/share/agent" # after specify init --integration generic --integration-options="--commands-dir .agent/commands"
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
root = project_root.resolve()
commands_dir = opts["commands_dir"]
dest = (root / commands_dir).resolve()
try:
dest.relative_to(root)
except ValueError:
raise SystemExit(f"commands_dir {commands_dir!r} escapes project root") Type guard
def is_in_project(root: Path, rel: str) -> bool:
try:
(root / rel).resolve().relative_to(root.resolve())
return True
except ValueError:
return False Try / catch
try:
integration.setup(root, manifest, parsed_options=opts)
except ValueError as e:
if "escapes project root" in str(e):
opts["commands_dir"] = ".agent/commands" # safe default, retry once
integration.setup(root, manifest, parsed_options=opts)
else:
raise Prevention
- Use relative commands_dir values without '..' or leading slashes.
- Ensure no component of the destination is a symlink pointing outside the project.
- Treat global/shared install locations as unsupported for commands_dir.
When it happens
Trigger: Passing parsed_options={"commands_dir": "../outside"} or an absolute path like "/etc/agent"; or a commands_dir whose first component is a symlink pointing outside the project, so resolve() escapes the root.
Common situations: Users trying to install commands into a shared/global directory; dotfile-symlinked directories (e.g. .myagent -> ~/dotfiles/.myagent); malformed --commands-dir values with leading slashes or '..'.
Related errors
- Command entry 'file' must be a relative path without '..': {
- Script entry must be a relative path without '..': {script_e
- Integration destination {dest_resolved} escapes project root
- Integration destination {dest} escapes project root {project
- --commands-dir is required for the generic integration
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/e4bab8ecc323edfb.
Report an issue: GitHub.