diff --git a/backend/packages/harness/deerflow/config/sandbox_config.py b/backend/packages/harness/deerflow/config/sandbox_config.py index 4ce45a4ace..cc42e380a1 100644 --- a/backend/packages/harness/deerflow/config/sandbox_config.py +++ b/backend/packages/harness/deerflow/config/sandbox_config.py @@ -236,6 +236,7 @@ class SandboxConfig(BaseModel): health_check_skip_seconds: float | None = Field( default=None, ge=0, + allow_inf_nan=False, description="BoxLite-only reclaim skip window in seconds for boxes recently released by this provider instance. Set to 0 to always validate before warm reuse.", ) ownership: SandboxOwnershipConfig | None = Field( diff --git a/backend/tests/test_boxlite_provider.py b/backend/tests/test_boxlite_provider.py index 736d6d45e6..138fbf56ef 100644 --- a/backend/tests/test_boxlite_provider.py +++ b/backend/tests/test_boxlite_provider.py @@ -18,9 +18,11 @@ import time import types import pytest +from pydantic import ValidationError from deerflow.community.boxlite.box import BoxliteBox from deerflow.community.boxlite.provider import BoxliteProvider, _import_simplebox +from deerflow.config.sandbox_config import SandboxConfig from deerflow.trace_context import get_current_trace_id, request_trace_context _LEGACY_COLLIDING_IDENTITIES = ( @@ -543,6 +545,25 @@ async def test_acquire_async_propagates_request_trace_context(monkeypatch): await asyncio.to_thread(provider.shutdown) +@pytest.mark.parametrize("value", [float("inf"), "1e999"]) +def test_sandbox_config_rejects_non_finite_health_check_skip_seconds(value): + """`ge=0` alone lets a non-finite skip window through. + + `health_check_skip_seconds: 1e999` in config.yaml loads as the string + "1e999" and pydantic's float coercion turns it into inf; `ge=0` accepts + inf (inf >= 0). At the warm-reuse site the check `skip_seconds > 0 and + (now - released_at) < skip_seconds` then holds forever, so recently + released boxes are promoted without the health check permanently instead + of failing fast at config load. `allow_inf_nan=False` (as already set on + the sibling sandbox float fields) makes the config layer reject it. + """ + with pytest.raises(ValidationError): + SandboxConfig( + use="deerflow.community.boxlite.provider:BoxliteProvider", + health_check_skip_seconds=value, + ) + + def test_explicit_recent_reclaim_skip_avoids_health_check(monkeypatch): """A configured skip window can reclaim recently released boxes without a ping.""" monkeypatch.setattr(