Skip to content

Commit 2c58a51

Browse files
jawwad-aliclaude
andcommitted
fix(workflows): reject infinite number-input default instead of raising OverflowError
WorkflowEngine._coerce_input normalizes a whole-valued number to int via int(value). For an infinite float (e.g. a 'type: number' input with YAML 'default: .inf') int(inf) raises OverflowError, which is not in the except (ValueError, TypeError) tuple. validate_workflow eager-coerces declared defaults and is documented to RETURN a list of errors, but it only catches ValueError -- so the OverflowError escaped and validate_workflow raised instead of reporting, breaking its contract. (NaN already surfaced cleanly because int(nan) raises ValueError.) Add OverflowError to the except tuple so an infinite default surfaces as the same clean 'expected a number' ValueError as NaN, consistent with the function's existing fail-fast-on-authoring-mistakes design. Finite values (5.0 -> 5, 3.5 -> 3.5) are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b7e67f5 commit 2c58a51

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/specify_cli/workflows/engine.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,12 @@ def _coerce_input(
10101010
value = float(value)
10111011
if value == int(value):
10121012
value = int(value)
1013-
except (ValueError, TypeError):
1013+
except (ValueError, TypeError, OverflowError):
1014+
# OverflowError: `int(value)` raises it for an infinite float
1015+
# (e.g. a `default: .inf` authoring mistake), which would
1016+
# otherwise escape validate_workflow's `except ValueError` and
1017+
# break its "return errors, never raise" contract. Surface it as
1018+
# the same clean "expected a number" error as NaN does.
10141019
msg = f"Input {name!r} expected a number, got {value!r}."
10151020
raise ValueError(msg) from None
10161021
elif input_type == "boolean":

tests/test_workflows.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,6 +2810,47 @@ def test_validate_workflow_rejects_bool_default_for_number_type(self):
28102810
errors = validate_workflow(definition)
28112811
assert any("invalid default" in e for e in errors), errors
28122812

2813+
def test_coerce_number_input_rejects_infinity_cleanly(self):
2814+
"""An infinite float must surface as a clean ValueError (like NaN), not
2815+
let ``int(inf)``'s OverflowError escape: ``int()`` of an infinity raises
2816+
OverflowError, which is not ValueError/TypeError.
2817+
"""
2818+
from specify_cli.workflows.engine import WorkflowEngine
2819+
2820+
for value in (float("inf"), float("-inf"), "inf", "Infinity", "-inf"):
2821+
with pytest.raises(ValueError, match="expected a number"):
2822+
WorkflowEngine._coerce_input("count", value, {"type": "number"})
2823+
# Finite values still coerce (whole floats normalize to int).
2824+
assert WorkflowEngine._coerce_input("count", 5.0, {"type": "number"}) == 5
2825+
assert WorkflowEngine._coerce_input("count", 3.5, {"type": "number"}) == 3.5
2826+
2827+
def test_validate_workflow_rejects_infinite_default_for_number_type(self):
2828+
"""``type: number`` with an infinite default (YAML ``.inf``) must be
2829+
reported as an error, not raise. ``int(inf)`` raises OverflowError during
2830+
coercion, which previously escaped validate_workflow's ValueError handler
2831+
and broke its "return a list of errors" contract.
2832+
"""
2833+
from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow
2834+
2835+
definition = WorkflowDefinition.from_string("""
2836+
schema_version: "1.0"
2837+
workflow:
2838+
id: "inf-as-number"
2839+
name: "Inf As Number"
2840+
version: "1.0.0"
2841+
inputs:
2842+
count:
2843+
type: number
2844+
default: .inf
2845+
steps:
2846+
- id: noop
2847+
type: gate
2848+
message: "noop"
2849+
options: [approve]
2850+
""")
2851+
errors = validate_workflow(definition)
2852+
assert any("invalid default" in e for e in errors), errors
2853+
28132854
def test_validate_workflow_rejects_non_string_default_for_string_type(self):
28142855
"""``type: string`` must require an actual string — a numeric YAML
28152856
default like ``5`` would otherwise slip through unvalidated.

0 commit comments

Comments
 (0)