github/spec-kit · error · ValueError
Path {rel} resolves to {resolved} which is outside the proje
Error message
Path {rel} resolves to {resolved} which is outside the project root {root_resolved} What it means
Raised by _validate_rel_path in the integration manifest layer when a manifest-relative path, after resolving (root / rel), no longer sits under the resolved project root. The manifest may only track files inside the project so uninstall stays non-destructive. This is the generic escape error, distinct from the absolute-path and '..'-canonicality errors raised nearby.
Source
Thrown at src/specify_cli/integrations/manifest.py:44
return h.hexdigest()
def _validate_rel_path(rel: Path, root: Path) -> Path:
"""Resolve *rel* against *root* and verify it stays within *root*.
Raises ``ValueError`` if *rel* is absolute, contains ``..`` segments
that escape *root*, or otherwise resolves outside the project root.
"""
if rel.is_absolute():
raise ValueError(
f"Absolute paths are not allowed in manifests: {rel}"
)
resolved = (root / rel).resolve()
root_resolved = root.resolve()
try:
resolved.relative_to(root_resolved)
except ValueError:
raise ValueError(
f"Path {rel} resolves to {resolved} which is outside "
f"the project root {root_resolved}"
) 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)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Use a plain canonical relative path with no '..' components (e.g. '.claude/commands/speckit.build.md')
- If the path was built by joining paths, normalize it first with Path(os.path.normpath(p)) and verify it stays relative to root
- Check for symlinked ancestors along the path and replace the symlink with a real directory or move the target inside the project
- Pass the same resolved project_root that the IntegrationManifest instance was constructed with
Example fix
// before
manifest.record_file("../shared/commands/build.md")
// after
manifest.record_file("shared/commands/build.md") # move/copy the file inside project root Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def safe_rel(root: Path, rel: str | Path) -> Path | None:
p = Path(rel)
if p.is_absolute() or ".." in p.parts:
return None
root_r = root.resolve()
try:
resolved = (root / p).resolve()
resolved.relative_to(root_r)
except (OSError, ValueError):
return None
if any((root / part).is_symlink() for part in _accum(p)):
return None
return resolved Type guard
def is_canonical_rel_path(rel: str) -> bool:
p = pathlib.PurePosixPath(rel)
return not p.is_absolute() and ".." not in p.parts and not rel.startswith("~") Try / catch
try:
manifest.record_file(rel)
except ValueError as exc:
if "outside the project root" in str(exc):
log.warning("skipping out-of-project path %s", rel)
else:
raise Prevention
- Build manifest paths only via path.relative_to(project_root)
- Never string-concatenate '..' into manifest rel paths
- Reject symlinked ancestors before recording files
When it happens
Trigger: Calling IntegrationManifest.record_file()/record_existing()/check_modified()/uninstall() with a rel_path containing '..' segments that escape root (e.g. '../../etc/passwd'), or a path whose intermediate directory is a symlink pointing outside the project, so resolve() lands outside root_resolved.
Common situations: Hand-built rel_path joining a template dir with '..' segments; a project dir containing symlinked subdirectories (monorepo shared folders, dotfile managers); passing a path computed against a different (non-resolved) project_root.
Related errors
- Invalid command 'file' {label}: {reason}
- Invalid alias {alias!r} for command '{cmd['name']}': {alias_
- Invalid {singular} 'file' {label}: {reason}
- Absolute paths are not allowed in manifests: {rel}
- Refusing to use symlinked integration manifest directory: {l
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/4870d5b16680ed7a.
Report an issue: GitHub.