From dc79389afa693d98676bacfa41c68c0be0808369 Mon Sep 17 00:00:00 2001 From: Ziiii <273036393+zixuanguo786-ctrl@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:42:30 +0800 Subject: [PATCH] Reject non-finite duration values Numeric wait durations accepted NaN and infinity values before schema-level bounds could make sense of them. Rejecting non-finite values keeps duration parsing predictable for wait and future duration-aware features. Constraint: Preserve existing valid numeric and suffixed duration syntax. Rejected: Let callers handle NaN and infinity | each caller would need to remember a subtle floating-point guard. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep duration parsing strict about values that cannot represent real elapsed time. Tested: uv run pytest tests/test_engine/test_duration.py -q Tested: uv run ruff check src/conductor/duration.py tests/test_engine/test_duration.py Not-tested: Full workflow execution with wait steps. --- src/conductor/duration.py | 5 +++++ tests/test_engine/test_duration.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/conductor/duration.py b/src/conductor/duration.py index 1f70d377..4f12560e 100644 --- a/src/conductor/duration.py +++ b/src/conductor/duration.py @@ -14,6 +14,7 @@ from __future__ import annotations +import math import re _DURATION_PATTERN = re.compile(r"^\s*(?P\d+(?:\.\d+)?)\s*(?Pms|s|m|h)?\s*$") @@ -64,6 +65,8 @@ def parse_duration(value: str | int | float) -> float: raise ValueError(f"duration must be a number or duration string, not boolean: {value!r}") if isinstance(value, (int, float)): + if not math.isfinite(value): + raise ValueError(f"duration must be finite, got {value!r}") return float(value) if not isinstance(value, str): @@ -79,5 +82,7 @@ def parse_duration(value: str | int | float) -> float: ) number = float(match.group("value")) + if not math.isfinite(number): + raise ValueError(f"duration must be finite, got {value!r}") unit = match.group("unit") or "s" return number * _UNIT_TO_SECONDS[unit] diff --git a/tests/test_engine/test_duration.py b/tests/test_engine/test_duration.py index ce0038c8..7d4cb2a6 100644 --- a/tests/test_engine/test_duration.py +++ b/tests/test_engine/test_duration.py @@ -6,6 +6,8 @@ from __future__ import annotations +import math + import pytest from conductor.duration import parse_duration @@ -118,3 +120,12 @@ def test_list(self) -> None: def test_number_then_garbage(self) -> None: with pytest.raises(ValueError): parse_duration("5 minutes") + + @pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf]) + def test_nonfinite_number(self, value: float) -> None: + with pytest.raises(ValueError, match="finite"): + parse_duration(value) + + def test_overflowing_string_number(self) -> None: + with pytest.raises(ValueError, match="finite"): + parse_duration("9" * 400)