github/spec-kit · error · SystemExit

Error: --number must be a non-negative integer

Error message

Error: --number must be a non-negative integer

What it means

During the directory walk, an existing component resolved (Path.resolve) to a location outside the resolved project root. This fires when the path is not itself a symlink but still lands outside the root — e.g. a hardlink-style bind mount, a mount point, or a symlink deeper in the chain that only resolve() exposes. The write is refused to keep shared infra inside the project.

Source

Thrown at extensions/git/scripts/python/create_new_feature_branch.py:115

        elif arg == "--dry-run":
            args.dry_run = True
        elif arg == "--allow-existing-branch":
            args.allow_existing = True
        elif arg == "--short-name":
            if i + 1 >= len(argv) or argv[i + 1].startswith("--"):
                _err("Error: --short-name requires a value")
                raise SystemExit(1)
            i += 1
            args.short_name = argv[i]
        elif arg == "--number":
            if i + 1 >= len(argv) or argv[i + 1].startswith("--"):
                _err("Error: --number requires a value")
                raise SystemExit(1)
            i += 1
            args.branch_number = argv[i]
            if not re.fullmatch(r"[0-9]+", args.branch_number):
                _err("Error: --number must be a non-negative integer")
                raise SystemExit(1)
        elif arg == "--timestamp":
            args.use_timestamp = True
        elif arg in ("--help", "-h"):
            print(HELP_TEXT)
            raise SystemExit(0)
        else:
            args.description_parts.append(arg)
        i += 1
    return args


# ── Core helpers loading ─────────────────────────────────────────────────────


def _find_project_root(start: Path) -> Path | None:
    current = start
    while True:
        if (current / ".specify").is_dir() or (current / ".git").exists():

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Pass the fully resolved root: use Path(project_path).resolve() before calling the API so root and components agree
  2. Replace mounts/junctions inside the project tree with real directories containing the actual data
  3. On WSL/containers, ensure the checkout lives on a native filesystem path, not a mounted host path with redirecting semantics
  4. Reproduce with Path(p).resolve() on each component in a REPL to find which one leaves the root

Example fix

# before
run(project_path=Path('/tmp/proj'))  # /tmp is a symlink on macOS

# after
run(project_path=Path('/tmp/proj').resolve())  # /private/tmp/proj, consistent with resolve() inside
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

root = project_path.resolve()
for cur in [root] + [root.joinpath(*rel_dir.parts[:i+1]) for i in range(len(rel_dir.parts))]:
    if cur.exists() and not cur.is_symlink():
        assert cur.resolve().is_relative_to(root), f'{cur} resolves outside root'

Try / catch

try:
    _ensure_safe_shared_directory(project_path, directory)
except ValueError as e:
    if 'escapes project root' in str(e):
        # pass a resolved root, or remove mounts/junctions inside the tree
        raise
    raise

Prevention

When it happens

Trigger: An intermediate directory under project_path is a mount point or was created via bind mount pointing elsewhere; or the component is a junction (Windows) that is_symlink() does not flag but resolve() relocates outside root; or project_path itself is passed unresolved while another component resolves through a link.

Common situations: Devcontainers/WSL where directories are bind-mounted from the Windows host; macOS Finder aliases or network mounts placed inside the repo; running with a project_path that is itself under a symlink (e.g. /tmp -> /private/tmp) so root resolution disagrees with component resolution.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/b09ca2d0c983d948. Report an issue: GitHub.