github/spec-kit · error · ValueError
Integration manifest directory escapes project root: {label}
Error message
Integration manifest directory escapes project root: {label} What it means
Raised by _ensure_safe_manifest_directory when the manifest directory itself is not even lexically relative to the project root (directory.relative_to(root) raises). This guards manifest writes (.specify/integrations/<key>.manifest.json) so the CLI never creates directories outside the project.
Source
Thrown at src/specify_cli/integrations/manifest.py:65
) from None
return resolved
def _manifest_path_label(root: Path, path: Path) -> str:
try:
return path.relative_to(root).as_posix()
except ValueError:
return path.as_posix()
def _ensure_safe_manifest_directory(root: Path, directory: Path) -> None:
"""Create a manifest directory without following symlinked parents."""
root_resolved = root.resolve()
try:
rel = directory.relative_to(root)
except ValueError:
label = _manifest_path_label(root, directory)
raise ValueError(f"Integration manifest directory escapes project root: {label}") from None
current = root
for part in rel.parts:
current = current / part
label = _manifest_path_label(root, current)
if current.is_symlink():
raise ValueError(f"Refusing to use symlinked integration manifest directory: {label}")
if current.exists():
if not current.is_dir():
raise ValueError(f"Integration manifest directory path is not a directory: {label}")
try:
current.resolve().relative_to(root_resolved)
except (OSError, ValueError):
raise ValueError(f"Integration manifest directory escapes project root: {label}") from None
continue
current.mkdir()
try:
current.resolve().relative_to(root_resolved)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Pass an absolute, resolved project_root (Path.cwd().resolve()) when constructing IntegrationManifest
- Ensure the manifest directory is derived from the same root instance (use manifest.manifest_path rather than recomputing it)
- In tests, derive expected paths from the same tmp_path object passed to the constructor
Example fix
// before
manifest = IntegrationManifest("claude", Path("."))
// after
manifest = IntegrationManifest("claude", Path.cwd().resolve()) Defensive patterns
Strategy: validation
Validate before calling
root = Path(project_root).resolve()
manifest_dir = root / ".specify" / "integrations"
try:
manifest_dir.relative_to(root)
except ValueError:
raise SystemExit(f"bad root: {manifest_dir} not under {root}") Try / catch
try:
IntegrationManifest(key, root).save()
except ValueError as exc:
if "escapes project root" in str(exc):
fix_root_and_retry() # re-construct with resolved absolute root
else:
raise Prevention
- Always pass an absolute, .resolve()-d project_root
- Derive the manifest dir from manifest.manifest_path instead of recomputing
- In tests, reuse the tmp_path object for both root and assertions
When it happens
Trigger: Constructing an IntegrationManifest whose project_root does not prefix the computed manifest directory — e.g. project_root is a subdirectory while the manifest dir was computed from a parent, or root/path mismatch after moving or symlinking the repo.
Common situations: project_root passed as a relative or unresolved path that differs from the absolute manifest directory; tests using tmp_path fixtures with mismatched roots; repos where the working directory was changed between construction and save().
Related errors
- manifest.project_root ({manifest.project_root}) does not mat
- Path {rel} resolves to {resolved} which is outside the proje
- Refusing to use symlinked integration manifest directory: {l
- Integration manifest directory path is not a directory: {lab
- Refusing to overwrite symlinked integration manifest path: {
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/44ce6c6b6de87007.
Report an issue: GitHub.