From 8bcee1b41219e153a3173fa9a7a027476400acce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:19:55 +0000 Subject: [PATCH 1/3] Fix oversized-number-crash: bound Python --number parsing Apply the remediation from the bug assessment on issue #5194. Refs #5194 Assisted-by: GitHub Copilot (model: gpt-5.3-codex, autonomous) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../python/create_new_feature_branch.py | 26 ++++++++- .../git/test_git_extension_python_parity.py | 55 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/extensions/git/scripts/python/create_new_feature_branch.py b/extensions/git/scripts/python/create_new_feature_branch.py index 202b1f796e..6025416468 100644 --- a/extensions/git/scripts/python/create_new_feature_branch.py +++ b/extensions/git/scripts/python/create_new_feature_branch.py @@ -24,6 +24,7 @@ SCRIPT_DIR = Path(__file__).resolve().parent MAX_BRANCH_LENGTH = 244 # GitHub enforces a 244-byte limit on branch names +MAX_FEATURE_NUMBER = 2**63 - 1 USAGE = ( "Usage: create_new_feature_branch.py [--json] [--dry-run] " @@ -76,6 +77,16 @@ def _persist_hint(var_name: str, value: str) -> str: return f"export {var_name}={escaped_value}" +def _int64_from_digits(value: str) -> int | None: + normalized = value.lstrip("0") or "0" + maximum = str(MAX_FEATURE_NUMBER) + if len(normalized) > len(maximum) or ( + len(normalized) == len(maximum) and normalized > maximum + ): + return None + return int(normalized, 10) + + @dataclass class Args: json_mode: bool = False @@ -113,6 +124,12 @@ def parse_args(argv: list[str]) -> Args: if not re.fullmatch(r"[0-9]+", args.branch_number): _err("Error: --number must be a non-negative integer") raise SystemExit(1) + if _int64_from_digits(args.branch_number) is None: + _err( + "Error: --number must be between 0 and " + f"{MAX_FEATURE_NUMBER}, got '{args.branch_number}'" + ) + raise SystemExit(1) elif arg == "--timestamp": args.use_timestamp = True elif arg in ("--help", "-h"): @@ -529,7 +546,14 @@ def build_branch_name(feature_num: str, branch_suffix: str) -> str: else: branch_number = get_highest_from_specs(specs_dir) + 1 - feature_num = f"{int(branch_number):03d}" + try: + feature_num = f"{int(branch_number):03d}" + except ValueError: + _err( + "Error: --number must be between 0 and " + f"{MAX_FEATURE_NUMBER}, got '{branch_number}'" + ) + return 1 branch_name = build_branch_name(feature_num, branch_suffix) branch_byte_len = _byte_length(branch_name) diff --git a/tests/extensions/git/test_git_extension_python_parity.py b/tests/extensions/git/test_git_extension_python_parity.py index 8cae45e783..06a61c63e6 100644 --- a/tests/extensions/git/test_git_extension_python_parity.py +++ b/tests/extensions/git/test_git_extension_python_parity.py @@ -299,6 +299,61 @@ def test_explicit_number(self, tmp_path: Path): _assert_parity(b, p) assert json.loads(p.stdout)["FEATURE_NUM"] == "042" + def test_explicit_number_max_int64_accepted(self, tmp_path: Path): + _, py_proj = _twin_projects(tmp_path) + p = _run_py( + "create-new-feature-branch", + py_proj, + "--json", + "--dry-run", + "--number", + str(2**63 - 1), + "some feature", + ) + assert p.returncode == 0, p.stderr + assert json.loads(p.stdout)["FEATURE_NUM"] == str(2**63 - 1) + + def test_explicit_number_above_max_int64_rejected(self, tmp_path: Path): + """Pin Python-side bound checking; bash currently has no equivalent guard.""" + _, py_proj = _twin_projects(tmp_path) + too_large = str(2**63) + p = _run_py( + "create-new-feature-branch", + py_proj, + "--json", + "--dry-run", + "--number", + too_large, + "some feature", + ) + assert p.returncode == 1 + assert ( + "Error: --number must be between 0 and 9223372036854775807, " + f"got '{too_large}'" + ) in p.stderr + assert "Traceback" not in p.stderr + + def test_explicit_number_oversized_digits_rejected_without_traceback( + self, tmp_path: Path + ): + _, py_proj = _twin_projects(tmp_path) + oversized = "9" * 5000 + p = _run_py( + "create-new-feature-branch", + py_proj, + "--json", + "--dry-run", + "--number", + oversized, + "some feature", + ) + assert p.returncode == 1 + assert ( + "Error: --number must be between 0 and 9223372036854775807, " + f"got '{oversized}'" + ) in p.stderr + assert "Traceback" not in p.stderr + def test_timestamp_mode_format(self, tmp_path: Path): _, py_proj = _twin_projects(tmp_path) p = _run_py( From 7bda56998dfd0201f3736d1c23c7024a2971ab18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:49:52 +0000 Subject: [PATCH 2/3] Fix zero-padded Git branch numbers Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> --- extensions/catalog.json | 2 +- extensions/git/extension.yml | 2 +- .../scripts/python/create_new_feature_branch.py | 4 +++- .../git/test_git_extension_python_parity.py | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/extensions/catalog.json b/extensions/catalog.json index fed3f08842..4194e8084d 100644 --- a/extensions/catalog.json +++ b/extensions/catalog.json @@ -51,7 +51,7 @@ "git": { "name": "Git Branching Workflow", "id": "git", - "version": "1.0.1", + "version": "1.0.2", "description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection", "author": "spec-kit-core", "repository": "https://github.com/github/spec-kit", diff --git a/extensions/git/extension.yml b/extensions/git/extension.yml index 90a7e56769..2d762a126a 100644 --- a/extensions/git/extension.yml +++ b/extensions/git/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: git name: "Git Branching Workflow" - version: "1.0.1" + version: "1.0.2" description: "Feature branch creation, numbering (sequential/timestamp), templating, validation, and Git remote detection" author: spec-kit-core repository: https://github.com/github/spec-kit diff --git a/extensions/git/scripts/python/create_new_feature_branch.py b/extensions/git/scripts/python/create_new_feature_branch.py index 6025416468..d4ea31f9e7 100644 --- a/extensions/git/scripts/python/create_new_feature_branch.py +++ b/extensions/git/scripts/python/create_new_feature_branch.py @@ -124,12 +124,14 @@ def parse_args(argv: list[str]) -> Args: if not re.fullmatch(r"[0-9]+", args.branch_number): _err("Error: --number must be a non-negative integer") raise SystemExit(1) - if _int64_from_digits(args.branch_number) is None: + normalized_number = _int64_from_digits(args.branch_number) + if normalized_number is None: _err( "Error: --number must be between 0 and " f"{MAX_FEATURE_NUMBER}, got '{args.branch_number}'" ) raise SystemExit(1) + args.branch_number = str(normalized_number) elif arg == "--timestamp": args.use_timestamp = True elif arg in ("--help", "-h"): diff --git a/tests/extensions/git/test_git_extension_python_parity.py b/tests/extensions/git/test_git_extension_python_parity.py index 06a61c63e6..647e19ea24 100644 --- a/tests/extensions/git/test_git_extension_python_parity.py +++ b/tests/extensions/git/test_git_extension_python_parity.py @@ -313,6 +313,20 @@ def test_explicit_number_max_int64_accepted(self, tmp_path: Path): assert p.returncode == 0, p.stderr assert json.loads(p.stdout)["FEATURE_NUM"] == str(2**63 - 1) + def test_explicit_number_zero_padded_input_accepted(self, tmp_path: Path): + _, py_proj = _twin_projects(tmp_path) + p = _run_py( + "create-new-feature-branch", + py_proj, + "--json", + "--dry-run", + "--number", + "0" * 5000 + "1", + "some feature", + ) + assert p.returncode == 0, p.stderr + assert json.loads(p.stdout)["FEATURE_NUM"] == "001" + def test_explicit_number_above_max_int64_rejected(self, tmp_path: Path): """Pin Python-side bound checking; bash currently has no equivalent guard.""" _, py_proj = _twin_projects(tmp_path) From 4faef8989cf31f5124c4f9ca2235b5311eb5b647 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Nov 2026 23:28:34 +0000 Subject: [PATCH 3/3] Reject exhausted auto-detected feature numbers Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com> --- extensions/catalog.json | 2 +- extensions/git/extension.yml | 2 +- .../scripts/python/create_new_feature_branch.py | 11 +++++++++-- .../git/test_git_extension_python_parity.py | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/extensions/catalog.json b/extensions/catalog.json index 4194e8084d..8ae8586f28 100644 --- a/extensions/catalog.json +++ b/extensions/catalog.json @@ -51,7 +51,7 @@ "git": { "name": "Git Branching Workflow", "id": "git", - "version": "1.0.2", + "version": "1.0.3", "description": "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection", "author": "spec-kit-core", "repository": "https://github.com/github/spec-kit", diff --git a/extensions/git/extension.yml b/extensions/git/extension.yml index 2d762a126a..cf3f728b77 100644 --- a/extensions/git/extension.yml +++ b/extensions/git/extension.yml @@ -3,7 +3,7 @@ schema_version: "1.0" extension: id: git name: "Git Branching Workflow" - version: "1.0.2" + version: "1.0.3" description: "Feature branch creation, numbering (sequential/timestamp), templating, validation, and Git remote detection" author: spec-kit-core repository: https://github.com/github/spec-kit diff --git a/extensions/git/scripts/python/create_new_feature_branch.py b/extensions/git/scripts/python/create_new_feature_branch.py index d4ea31f9e7..7ceaace0e3 100644 --- a/extensions/git/scripts/python/create_new_feature_branch.py +++ b/extensions/git/scripts/python/create_new_feature_branch.py @@ -549,13 +549,20 @@ def build_branch_name(feature_num: str, branch_suffix: str) -> str: branch_number = get_highest_from_specs(specs_dir) + 1 try: - feature_num = f"{int(branch_number):03d}" + feature_number = int(branch_number) except ValueError: _err( - "Error: --number must be between 0 and " + "Error: feature number must be between 0 and " + f"{MAX_FEATURE_NUMBER}, got '{branch_number}'" + ) + return 1 + if not 0 <= feature_number <= MAX_FEATURE_NUMBER: + _err( + "Error: feature number must be between 0 and " f"{MAX_FEATURE_NUMBER}, got '{branch_number}'" ) return 1 + feature_num = f"{feature_number:03d}" branch_name = build_branch_name(feature_num, branch_suffix) branch_byte_len = _byte_length(branch_name) diff --git a/tests/extensions/git/test_git_extension_python_parity.py b/tests/extensions/git/test_git_extension_python_parity.py index 647e19ea24..54333b5671 100644 --- a/tests/extensions/git/test_git_extension_python_parity.py +++ b/tests/extensions/git/test_git_extension_python_parity.py @@ -368,6 +368,23 @@ def test_explicit_number_oversized_digits_rejected_without_traceback( ) in p.stderr assert "Traceback" not in p.stderr + def test_auto_detected_number_above_max_int64_rejected(self, tmp_path: Path): + _, py_proj = _twin_projects(tmp_path) + (py_proj / "specs" / f"{2**63 - 1}-existing").mkdir(parents=True) + p = _run_py( + "create-new-feature-branch", + py_proj, + "--json", + "--dry-run", + "some feature", + ) + assert p.returncode == 1 + assert ( + "Error: feature number must be between 0 and 9223372036854775807, " + "got '9223372036854775808'" + ) in p.stderr + assert "Traceback" not in p.stderr + def test_timestamp_mode_format(self, tmp_path: Path): _, py_proj = _twin_projects(tmp_path) p = _run_py(