Skip to content

Commit f66f042

Browse files
jawwad-aliclaude
andcommitted
fix(workflows): reject bool max_iterations in while/do-while validation
while/do-while validate() checked 'not isinstance(max_iter, int) or max_iter < 1'. Since bool is a subclass of int, isinstance(True, int) is True and True < 1 is False, so 'max_iterations: true' passed validation and then ran as a single iteration (range(True) == range(1)) instead of being reported as a type error. Reject bools explicitly, matching the fail-fast-on-bool handling already used for number inputs and gate options. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5bdcb4a commit f66f042

3 files changed

Lines changed: 29 additions & 2 deletions

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ def validate(self, config: dict[str, Any]) -> list[str]:
4848
)
4949
max_iter = config.get("max_iterations")
5050
if max_iter is not None:
51-
if not isinstance(max_iter, int) or max_iter < 1:
51+
# bool is a subclass of int, so isinstance(True, int) is True and
52+
# True < 1 is False; reject bools explicitly so `max_iterations: true`
53+
# is a type error rather than a silent single iteration.
54+
if isinstance(max_iter, bool) or not isinstance(max_iter, int) or max_iter < 1:
5255
errors.append(
5356
f"Do-while step {config.get('id', '?')!r}: "
5457
f"'max_iterations' must be an integer >= 1."

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ def validate(self, config: dict[str, Any]) -> list[str]:
5555
)
5656
max_iter = config.get("max_iterations")
5757
if max_iter is not None:
58-
if not isinstance(max_iter, int) or max_iter < 1:
58+
# bool is a subclass of int, so isinstance(True, int) is True and
59+
# True < 1 is False; reject bools explicitly so `max_iterations: true`
60+
# is a type error rather than a silent single iteration.
61+
if isinstance(max_iter, bool) or not isinstance(max_iter, int) or max_iter < 1:
5962
errors.append(
6063
f"While step {config.get('id', '?')!r}: "
6164
f"'max_iterations' must be an integer >= 1."

tests/test_workflows.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,6 +1784,12 @@ def test_validate_invalid_max_iterations(self):
17841784
step = WhileStep()
17851785
errors = step.validate({"id": "test", "condition": "{{ true }}", "max_iterations": 0, "steps": []})
17861786
assert any("must be an integer >= 1" in e for e in errors)
1787+
# bool is an int subclass; `max_iterations: true` must be rejected, not
1788+
# silently treated as a single iteration.
1789+
bool_errors = step.validate(
1790+
{"id": "test", "condition": "{{ true }}", "max_iterations": True, "steps": []}
1791+
)
1792+
assert any("must be an integer >= 1" in e for e in bool_errors)
17871793

17881794

17891795
class TestDoWhileStep:
@@ -1823,6 +1829,21 @@ def test_execute_with_true_condition(self):
18231829
assert len(result.next_steps) == 1
18241830
assert result.output["max_iterations"] == 5
18251831

1832+
def test_validate_rejects_bool_max_iterations(self):
1833+
from specify_cli.workflows.steps.do_while import DoWhileStep
1834+
1835+
step = DoWhileStep()
1836+
# bool is an int subclass; `max_iterations: true` must be rejected.
1837+
errors = step.validate(
1838+
{"id": "test", "condition": "{{ true }}", "max_iterations": True, "steps": []}
1839+
)
1840+
assert any("must be an integer >= 1" in e for e in errors)
1841+
# a real positive integer is still accepted.
1842+
ok = step.validate(
1843+
{"id": "test", "condition": "{{ true }}", "max_iterations": 3, "steps": []}
1844+
)
1845+
assert not any("max_iterations" in e for e in ok)
1846+
18261847
def test_execute_empty_steps(self):
18271848
from specify_cli.workflows.steps.do_while import DoWhileStep
18281849
from specify_cli.workflows.base import StepContext

0 commit comments

Comments
 (0)