github/spec-kit · error · SystemExit
Error: branch_template must put {number}- at the start of th
Error message
Error: branch_template must put {number}- at the start of the final path segment so generated branches remain valid feature branches. What it means
The post-creation branch of _ensure_safe_shared_directory(): after mkdir(), resolve() of the new directory does not stay under the resolved project root. Like error 464's pre-check, this guards against mounts/junctions that materialize outside the root, but here it fires after creation because the resolution only becomes visible once the directory exists.
Source
Thrown at extensions/git/scripts/python/create_new_feature_branch.py:388
_err(
"Error: branch_template must include the {number} token so generated "
"branches remain valid feature branches."
)
raise SystemExit(1)
slug_index = template.find("{slug}")
if slug_index != -1 and "{number}" in template[slug_index:]:
_err(
"Error: branch_template must not place {slug} before {number}; "
"use {slug} only in the final feature segment."
)
raise SystemExit(1)
feature_segment = template.rsplit("/", 1)[-1]
if not feature_segment.startswith("{number}-"):
_err(
"Error: branch_template must put {number}- at the start of the final "
"path segment so generated branches remain valid feature branches."
)
raise SystemExit(1)
def render_branch_template(
template: str, feature_num: str, branch_suffix: str, author_token: str, app_token: str
) -> str:
rendered = template
rendered = rendered.replace("{author}", author_token)
rendered = rendered.replace("{app}", app_token)
rendered = rendered.replace("{number}", feature_num)
rendered = rendered.replace("{slug}", branch_suffix)
return rendered
def extract_feature_num_from_branch(branch_name: str) -> str:
feature_segment = branch_name.rsplit("/", 1)[-1]
match = re.match(r"^[0-9]{8}-[0-9]{6}-", feature_segment)
if match:
return match.group(0).rstrip("-")View on GitHub (pinned to bf88c9f9a8)
Solutions
- Always pass project_path as Path(...).resolve() so the root matches what resolve() will produce for children
- Work in a native, non-redirecting directory (e.g. ~/work/proj instead of /tmp/proj on macOS)
- Avoid FUSE/mount-backed paths inside the project tree for .specify
- If reproducible only in CI, compare Path(p).resolve() for root and children in a debug step to find the redirect
Example fix
# before
run(project_path=Path('/tmp/proj'))
# after
run(project_path=Path('/tmp/proj').resolve()) # consistent with internal root resolution Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path root = project_path.resolve() # simulate what ensure will do after creation assert (root / rel_dir).resolve().is_relative_to(root) or not (root / rel_dir).exists()
Try / catch
try:
_ensure_safe_shared_directory(project_path, directory, create=True)
except ValueError as e:
if 'escapes project root' in str(e):
_ensure_safe_shared_directory(Path(project_path).resolve(), directory, create=True)
else:
raise Prevention
- Pass pre-resolved project roots everywhere
- Avoid autofs/FUSE-backed paths for the project and .specify
- On macOS, work under /Users/... rather than /tmp or /var/folders
When it happens
Trigger: The created directory sits on a mount whose resolution escapes root (autofs, FUSE, bind mounts); project_path passed unresolved while creation triggers a mount/symlink resolution (e.g. macOS /tmp vs /private/tmp autofs); container runtimes mounting over just-created paths.
Common situations: Running installs under /tmp or /var/folders on macOS (symlinked to /private/...); FUSE filesystems (sshfs, gcsfuse) inside the repo; sidecar containers that bind-mount into the workspace on file creation; CI with overlay filesystem quirks.
Related errors
- Error: --number must be a non-negative integer
- Error: branch_template must not place {slug} before {number}
- ERROR: SPECIFY_INIT_DIR does not point to an existing direct
- ERROR: SPECIFY_INIT_DIR is not a Spec Kit project (no .speci
- Error: --short-name requires a value
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/3fa2ae2a7ca7ef41.
Report an issue: GitHub.