mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(workflows): gate prompt uses isdecimal() so a superscript digit doesn't crash (#3624)
The interactive gate prompt guarded numeric choices with raw.isdigit(), but
str.isdigit() returns True for characters int() rejects — superscripts/subscripts
like '²'. So typing '²' passed the guard and int('²') raised an uncaught
ValueError, crashing the prompt loop. Use raw.isdecimal(), which is exactly the
decimal-digit set int() accepts (Numeric_Type=Decimal), so such input is treated
as an invalid choice and re-prompted. No behavior change for valid input.
Test: input '²' then '1' returns the first option (fails before: ValueError).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,7 +168,11 @@ class GateStep(StepBase):
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
return options[-1] # default to last (usually reject)
|
||||
if raw.isdigit() and 1 <= int(raw) <= len(options):
|
||||
# isdecimal() (not isdigit()): int() accepts exactly the decimal-digit
|
||||
# set, whereas isdigit() also returns True for superscripts/subscripts
|
||||
# (e.g. "²") that int() then rejects with ValueError — crashing
|
||||
# this interactive loop.
|
||||
if raw.isdecimal() and 1 <= int(raw) <= len(options):
|
||||
return options[int(raw) - 1]
|
||||
# Also accept the option name directly
|
||||
if raw.lower() in [o.lower() for o in options]:
|
||||
|
||||
@@ -2145,6 +2145,19 @@ class TestGateStep:
|
||||
assert result.status == StepStatus.COMPLETED
|
||||
assert result.output["choice"] == "approve"
|
||||
|
||||
def test_interactive_prompt_rejects_non_decimal_digit(self, monkeypatch, capsys):
|
||||
"""A Unicode digit int() can't parse — e.g. the superscript '²', which
|
||||
str.isdigit() accepts but int() rejects — must be treated as an invalid
|
||||
choice, not crash the prompt loop with an uncaught ValueError."""
|
||||
from specify_cli.workflows.steps.gate import GateStep
|
||||
|
||||
_force_gate_stdin(monkeypatch, tty=True)
|
||||
inputs = iter(["²", "1"]) # superscript-two, then a real "1"
|
||||
monkeypatch.setattr("builtins.input", lambda _prompt="": next(inputs))
|
||||
|
||||
choice = GateStep._prompt("Review the spec.", ["approve", "reject"])
|
||||
assert choice == "approve"
|
||||
|
||||
def test_interactive_prompt_missing_show_file_does_not_crash(
|
||||
self, tmp_path, monkeypatch, capsys
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user