Skip to content

Commit 9ef4771

Browse files
jawwad-aliclaude
andauthored
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>
1 parent d39f8fd commit 9ef4771

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

src/specify_cli/workflows/steps/gate/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ def _prompt(message: str, options: list[str]) -> str:
168168
except (EOFError, KeyboardInterrupt):
169169
print()
170170
return options[-1] # default to last (usually reject)
171-
if raw.isdigit() and 1 <= int(raw) <= len(options):
171+
# isdecimal() (not isdigit()): int() accepts exactly the decimal-digit
172+
# set, whereas isdigit() also returns True for superscripts/subscripts
173+
# (e.g. "²") that int() then rejects with ValueError — crashing
174+
# this interactive loop.
175+
if raw.isdecimal() and 1 <= int(raw) <= len(options):
172176
return options[int(raw) - 1]
173177
# Also accept the option name directly
174178
if raw.lower() in [o.lower() for o in options]:

tests/test_workflows.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,19 @@ def test_interactive_prompt_renders_show_file(self, tmp_path, monkeypatch, capsy
21452145
assert result.status == StepStatus.COMPLETED
21462146
assert result.output["choice"] == "approve"
21472147

2148+
def test_interactive_prompt_rejects_non_decimal_digit(self, monkeypatch, capsys):
2149+
"""A Unicode digit int() can't parse — e.g. the superscript '²', which
2150+
str.isdigit() accepts but int() rejects — must be treated as an invalid
2151+
choice, not crash the prompt loop with an uncaught ValueError."""
2152+
from specify_cli.workflows.steps.gate import GateStep
2153+
2154+
_force_gate_stdin(monkeypatch, tty=True)
2155+
inputs = iter(["²", "1"]) # superscript-two, then a real "1"
2156+
monkeypatch.setattr("builtins.input", lambda _prompt="": next(inputs))
2157+
2158+
choice = GateStep._prompt("Review the spec.", ["approve", "reject"])
2159+
assert choice == "approve"
2160+
21482161
def test_interactive_prompt_missing_show_file_does_not_crash(
21492162
self, tmp_path, monkeypatch, capsys
21502163
):

0 commit comments

Comments
 (0)