github/spec-kit · error · ValueError
Refusing to use symlinked integration manifest directory: {l
Error message
Refusing to use symlinked integration manifest directory: {label} What it means
Raised while walking each component of the manifest directory path when a component is a symlink. The CLI refuses to follow symlinked directories because mkdir/resolve could then write or mutate state outside the project root, defeating the manifest's containment guarantees.
Source
Thrown at src/specify_cli/integrations/manifest.py:72
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)
except (OSError, ValueError):
raise ValueError(f"Integration manifest directory escapes project root: {label}") from None
def _ensure_safe_manifest_destination(root: Path, path: Path) -> None:
"""Refuse manifest writes that would escape the project or follow symlinks."""
root_resolved = root.resolve()View on GitHub (pinned to bf88c9f9a8)
Solutions
- Remove the symlink and use a real directory (mkdir -p .specify/integrations)
- Point the symlink target inside the project, or bind-mount instead of symlinking
- If sharing manifests is the goal, copy them with a script instead of symlinking
Example fix
# before ln -s ~/dotfiles/specify .specify # after mkdir -p .specify/integrations && cp ~/dotfiles/specify/*.json .specify/integrations/
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def manifest_dir_is_safe(root: Path, directory: Path) -> bool:
try:
rel = directory.relative_to(root)
except ValueError:
return False
cur = root
for part in rel.parts:
cur = cur / part
if cur.is_symlink():
return False
return True Try / catch
try:
manifest.save()
except ValueError as exc:
if "symlinked integration manifest directory" in str(exc):
replace_symlink_with_real_dir(label_from(exc))
else:
raise Prevention
- Keep .specify as a real directory inside the repo
- Add .specify to .gitignore rules that might link it
- In CI, verify test fixtures create .specify with mkdir, not ln -s
When it happens
Trigger: Any component of .specify, .specify/integrations, or deeper being a symlink when save() creates the manifest directory; typically .specify symlinked to a shared or external location.
Common situations: Developers symlinking .specify into a dotfiles repo or a shared config dir; CI caches that restore .specify as a symlink; containerized setups mounting config over symlinked paths.
Related errors
- Refusing to overwrite symlinked integration manifest path: {
- Path {rel} resolves to {resolved} which is outside the proje
- Integration manifest path escapes project root: {label}
- Refusing to record symlinked manifest path: {rel} (symlinked
- Error: --short-name requires a value
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/5802c8702980f84f.
Report an issue: GitHub.