github/spec-kit · error · ValueError
Manifest paths must be canonical; '..' segments are not allo
Error message
Manifest paths must be canonical; '..' segments are not allowed (got {rel}) What it means
Raised by record_file()/record_existing() when the rel_path contains '..' segments, even if they would normalize back inside the project (e.g. 'dir/../file.txt'). Manifest keys must be canonical so check_modified() and uninstall() cannot address the same file under two different paths.
Source
Thrown at src/specify_cli/integrations/manifest.py:202
OSError: if the underlying filesystem call (``is_symlink``,
``is_file``, or the file-read used to compute the hash)
fails — for example a ``PermissionError`` on the path.
Callers should be prepared to handle ``OSError`` (and its
subclasses such as ``PermissionError``) in addition to
``ValueError``.
"""
rel = Path(rel_path)
# Cheap lexical pre-check first so absolute / parent-traversal paths
# don't trigger a filesystem stat outside the project root before
# ``_validate_rel_path`` raises. ``_validate_rel_path`` produces the
# canonical error messages used elsewhere.
if rel.is_absolute() or ".." in rel.parts:
_validate_rel_path(rel, self.project_root)
# _validate_rel_path raised for any actually-escaping path. If we reach
# here the path normalizes inside root (e.g. ``dir/../file.txt``).
# Reject anyway: manifest keys must be canonical so ``check_modified``
# and ``uninstall`` cannot key the same file under two paths.
raise ValueError(
f"Manifest paths must be canonical; '..' segments are not "
f"allowed (got {rel})"
)
# Walk each path component before resolution so a symlinked ancestor
# (e.g. ``linked_dir/file.txt`` where ``linked_dir`` is a symlink)
# cannot be silently followed by ``_validate_rel_path().resolve()``
# down to a target outside the project root. ``_ensure_safe_manifest_directory``
# uses the same pattern.
_walk = self.project_root
for part in rel.parts:
_walk = _walk / part
if _walk.is_symlink():
raise ValueError(
f"Refusing to record symlinked manifest path: {rel} "
f"(symlinked at {_walk.relative_to(self.project_root).as_posix()})"
)
abs_path = _validate_rel_path(rel, self.project_root)
if not abs_path.is_file():View on GitHub (pinned to bf88c9f9a8)
Solutions
- Normalize and relativize the path before recording: rel = Path(os.path.normpath(rel)) and strip any '..' that remain
- Compute the path relative to project_root with abs_path.relative_to(project_root) instead of string surgery
- Reject '..' paths at the boundary of your own code that feeds the manifest
Example fix
// before
manifest.record_file(".claude/commands/../commands/build.md")
// after
manifest.record_file(".claude/commands/build.md") Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
import os
rel = Path(os.path.normpath(str(rel)))
if rel.is_absolute() or ".." in rel.parts:
rel = Path.cwd().joinpath(rel).resolve().relative_to(project_root)
manifest.record_file(rel.as_posix()) Type guard
def is_canonical_manifest_key(rel: str) -> bool:
p = pathlib.PurePosixPath(rel)
return bool(rel) and not p.is_absolute() and ".." not in p.parts Try / catch
try:
manifest.record_file(rel)
except ValueError as exc:
if "must be canonical" in str(exc):
manifest.record_file(Path(os.path.normpath(rel)).as_posix())
else:
raise Prevention
- Always derive keys via abs.relative_to(project_root)
- Normalize paths at your API boundary before passing them in
- Add a lint check banning '..' in recorded manifest paths
When it happens
Trigger: Calling manifest.record_file('a/../b/cmd.md') or record_existing('.claude/commands/../commands/x.md'); the lexical pre-check sees '..' in rel.parts, delegates escaping paths to _validate_rel_path, and rejects the rest with this message.
Common situations: Paths assembled from template names that include '..' (e.g. '../shared/'); porting code that used os.path.join without normalizing; copy-pasted absolute paths converted to relative by stripping a prefix.
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}
- Path {rel} resolves to {resolved} which is outside the proje
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/ddfc9b5c6ff76a95.
Report an issue: GitHub.