{"record":{"id":"3fa2ae2a7ca7ef41","repo":"github/spec-kit","slug":"error-branch-template-must-put-number-at-the-s","errorCode":null,"errorMessage":"Error: branch_template must put {number}- at the start of the final path segment so generated branches remain valid feature branches.","messagePattern":"Error: branch_template must put (.+?)- at the start of the final path segment so generated branches remain valid feature branches\\.","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"extensions/git/scripts/python/create_new_feature_branch.py","lineNumber":388,"sourceCode":"        _err(\n            \"Error: branch_template must include the {number} token so generated \"\n            \"branches remain valid feature branches.\"\n        )\n        raise SystemExit(1)\n    slug_index = template.find(\"{slug}\")\n    if slug_index != -1 and \"{number}\" in template[slug_index:]:\n        _err(\n            \"Error: branch_template must not place {slug} before {number}; \"\n            \"use {slug} only in the final feature segment.\"\n        )\n        raise SystemExit(1)\n    feature_segment = template.rsplit(\"/\", 1)[-1]\n    if not feature_segment.startswith(\"{number}-\"):\n        _err(\n            \"Error: branch_template must put {number}- at the start of the final \"\n            \"path segment so generated branches remain valid feature branches.\"\n        )\n        raise SystemExit(1)\n\n\ndef render_branch_template(\n    template: str, feature_num: str, branch_suffix: str, author_token: str, app_token: str\n) -> str:\n    rendered = template\n    rendered = rendered.replace(\"{author}\", author_token)\n    rendered = rendered.replace(\"{app}\", app_token)\n    rendered = rendered.replace(\"{number}\", feature_num)\n    rendered = rendered.replace(\"{slug}\", branch_suffix)\n    return rendered\n\n\ndef extract_feature_num_from_branch(branch_name: str) -> str:\n    feature_segment = branch_name.rsplit(\"/\", 1)[-1]\n    match = re.match(r\"^[0-9]{8}-[0-9]{6}-\", feature_segment)\n    if match:\n        return match.group(0).rstrip(\"-\")","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/extensions/git/scripts/python/create_new_feature_branch.py#L370-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nrun(project_path=Path('/tmp/proj'))\n\n# after\nrun(project_path=Path('/tmp/proj').resolve())  # consistent with internal root resolution","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\nroot = project_path.resolve()\n# simulate what ensure will do after creation\nassert (root / rel_dir).resolve().is_relative_to(root) or not (root / rel_dir).exists()","typeGuard":null,"tryCatchPattern":"try:\n    _ensure_safe_shared_directory(project_path, directory, create=True)\nexcept ValueError as e:\n    if 'escapes project root' in str(e):\n        _ensure_safe_shared_directory(Path(project_path).resolve(), directory, create=True)\n    else:\n        raise","preventionTips":["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"],"tags":["filesystem","portability","race-condition","security"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}