fix(config): reject boolean integer settings in scheduler_config (#6258)

#6229 gave the scheduler its own integer knobs, but they accept YAML
booleans: recursion_limit: true loads as 1, so every scheduler-launched
run dies at its first LangGraph super-step - the same hole the
app-config guard already closes for Gateway runs (#6171), now on the
same-named field. max_concurrent_runs: true serializes the scheduler
and poll_interval_seconds: true polls five times a second.

Mirror the app-config boolean guard: a before-validator rejects booleans
with a field-specific error across all six integer fields, keeping
integers and numeric strings as-is. lease_seconds and
queue_timeout_seconds already reject true via their higher ge floors,
but with a misleading "should be greater than or equal to" message -
the guard gives every field the same boolean-specific error.

Signed-off-by: yetuge <2219677952@qq.com>
This commit is contained in:
yetuge 2026-10-04 14:42:06 +08:00 committed by GitHub
parent 2e4191e56f
commit 23e69a0c94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View File

@ -1,4 +1,6 @@
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, ValidationInfo, field_validator
from deerflow.config._boolean_guards import reject_boolean
class SchedulerConfig(BaseModel):
@ -21,3 +23,16 @@ class SchedulerConfig(BaseModel):
"AppConfig.max_recursion_limit are clamped."
),
)
@field_validator(
"poll_interval_seconds",
"lease_seconds",
"max_concurrent_runs",
"queue_timeout_seconds",
"min_once_delay_seconds",
"recursion_limit",
mode="before",
)
@classmethod
def _reject_boolean_scheduler_integers(cls, value: object, info: ValidationInfo) -> object:
return reject_boolean(value, info, kind="an integer")

View File

@ -0,0 +1,42 @@
"""YAML booleans must not coerce into the scheduler's integer knobs.
`config.yaml` legitimately mixes booleans (`enabled: true`) with integer knobs on
neighbouring lines, so a typo like `scheduler.recursion_limit: true` loads
cleanly — and Pydantic coerces it to `1`, which makes every scheduler-launched
run hit the LangGraph recursion ceiling at its first super-step (the same hole
the app-config guard already closes for Gateway runs). `max_concurrent_runs: true`
serializes the scheduler and `poll_interval_seconds: true` polls five times a
second. Mirrors the app-config boolean guard.
"""
import pytest
from pydantic import ValidationError
from deerflow.config.scheduler_config import SchedulerConfig
SCHEDULER_INT_FIELDS = (
"poll_interval_seconds",
"lease_seconds",
"max_concurrent_runs",
"queue_timeout_seconds",
"min_once_delay_seconds",
"recursion_limit",
)
@pytest.mark.parametrize("value", [True, False])
@pytest.mark.parametrize("field", SCHEDULER_INT_FIELDS)
def test_scheduler_config_rejects_boolean_integers(field: str, value: bool) -> None:
with pytest.raises(ValidationError, match="must be an integer, not a boolean"):
SchedulerConfig(**{field: value})
def test_scheduler_config_keeps_numeric_inputs() -> None:
config = SchedulerConfig(
poll_interval_seconds="30",
max_concurrent_runs=5,
recursion_limit=500,
)
assert config.poll_interval_seconds == 30
assert config.max_concurrent_runs == 5
assert config.recursion_limit == 500