github/spec-kit · error · SystemExit
ERROR: SPECIFY_INIT_DIR is not a Spec Kit project (no .speci
Error message
ERROR: SPECIFY_INIT_DIR is not a Spec Kit project (no .specify/ directory): {init_root} What it means
Final check of _ensure_safe_shared_destination(): dest exists, is not a symlink, but its resolved location falls outside the resolved project root. This catches hardlink-adjacent redirections and mount scenarios at the file level (a non-symlink path whose canonical location escapes), blocking the write before data is clobbered.
Source
Thrown at scripts/python/common.py:56
except OSError:
print(
f"ERROR: SPECIFY_INIT_DIR does not point to an existing directory: {raw}",
file=sys.stderr,
)
raise SystemExit(1)
if not init_root.is_dir():
print(
f"ERROR: SPECIFY_INIT_DIR does not point to an existing directory: {raw}",
file=sys.stderr,
)
raise SystemExit(1)
if not (init_root / ".specify").is_dir():
print(
"ERROR: SPECIFY_INIT_DIR is not a Spec Kit project "
f"(no .specify/ directory): {init_root}",
file=sys.stderr,
)
raise SystemExit(1)
return init_root
def get_repo_root(script_file: Path | None = None) -> Path:
if os.environ.get("SPECIFY_INIT_DIR"):
return resolve_specify_init_dir()
specify_root = find_specify_root()
if specify_root is not None:
return specify_root
if script_file is not None:
script_root = find_specify_root(script_file.resolve().parent)
if script_root is not None:
return script_root
# Installed scripts live at .specify/scripts/python/<script>.py.
return script_file.resolve().parents[3]View on GitHub (pinned to bf88c9f9a8)
Solutions
- Pass project_path as Path(...).resolve()
- Replace mount/junction-backed destinations with real files on the project filesystem
- Move the working copy off redirecting temp/mount paths
- Verify with dest.resolve().relative_to(root) in a debug snippet to find the mismatch
Example fix
# before write(proj, proj/'.specify'/'shared'/'x.yaml') # x.yaml on a bind mount # after # remove mount, place real file, pass resolved root write(Path(proj).resolve(), proj/'.specify'/'shared'/'x.yaml')
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
root = project_path.resolve()
if dest.exists():
assert dest.resolve().is_relative_to(root), f'{dest} resolves outside project root' Try / catch
try:
_ensure_safe_shared_destination(project_path, dest)
except ValueError as e:
if 'destination escapes project root' in str(e):
_ensure_safe_shared_destination(Path(project_path).resolve(), dest)
else:
raise Prevention
- Keep .specify destinations on the same native filesystem as the project
- Avoid mount/junction-backed files at destination paths
- Pass resolved project roots to shared-infra APIs
When it happens
Trigger: dest exists on a mount/junction whose resolve() leaves root; dest created via bind mount; root passed unresolved while dest canonicalizes differently (e.g. project under /tmp on macOS).
Common situations: FUSE/mount-backed project trees; WSL/devcontainer host mounts; moving a project between machines with redirecting paths; CI overlay filesystems.
Related errors
- Error: --number must be a non-negative integer
- ERROR: SPECIFY_INIT_DIR does not point to an existing direct
- Error: --short-name requires a value
- Error: branch_template must not place {slug} before {number}
- Error: branch_template must put {number}- at the start of th
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/e150d9ee32055d1d.
Report an issue: GitHub.