Files
odysseus/tests/test_scheduler_research_settings_guard.py
T
ShahabAhmed01 35e3b298d8 fix(scheduler): fall back to defaults for non-numeric research settings
The scheduled-research task reads research_max_tokens,
research_extraction_timeout_seconds, and research_extraction_concurrency
with bare int() casts and no guard. A hand-edited or agent-written
data/settings.json value like {"research_max_tokens": "invalid"} raised
ValueError and killed every scheduled research run until settings.json was
fixed by hand (#5049). src/research_handler.py already guards the same reads
defensively (_bounded_int); this mirrors that for the scheduler.

Wrap the three reads in try/except (TypeError, ValueError) falling back to
the documented defaults 8192 / 90 / 3. Valid values (numeric strings like
"4096") still parse; the change is limited to the scheduler's reads (agent_loop
sites are tracked separately in #5051).

Child of maintainer subtracker #5335 (RaresKeY) whose acceptance criteria state:
"Non-numeric agent/scheduler settings fall back safely instead of crashing or
silently degrading." Prior PR #5050 attempted the same fix but was closed
unmerged in the 2026-07-23 repo-transfer bulk close and never resubmitted —
redoing it with credit.

Fixes #5049.
2026-10-03 23:23:36 +05:00

80 lines
2.4 KiB
Python

"""Guarded resolution of scheduled-research settings (#5049).
The scheduler reads ``research_max_tokens`` /
``research_extraction_timeout_seconds`` / ``research_extraction_concurrency``
from settings.json with bare ``int()`` casts. A hand-edited value like
``{"research_max_tokens": "invalid"}`` used to raise inside
``TaskScheduler._execute_research_task`` and kill every scheduled research
run until settings.json was fixed by hand.
``_resolve_research_settings`` must fall back to the documented defaults
(8192 / 90 / 3) for non-numeric input while preserving valid-value behavior
(numeric strings still parse). ``src/research_handler.py`` already guards the
same reads defensively; this is the scheduler's equivalent.
"""
from __future__ import annotations
from src.task_scheduler import _resolve_research_settings
def _fake_get_setting(values):
"""A get_setting stand-in backed by a plain dict."""
def get(key, default=None):
return values.get(key, default)
return get
def test_non_numeric_values_fall_back_to_documented_defaults():
get = _fake_get_setting(
{
"research_max_tokens": "invalid",
"research_extraction_timeout_seconds": "bogus",
"research_extraction_concurrency": "not-a-number",
}
)
assert _resolve_research_settings(get) == (8192, 90, 3)
def test_none_values_fall_back_to_documented_defaults():
get = _fake_get_setting(
{
"research_max_tokens": None,
"research_extraction_timeout_seconds": None,
"research_extraction_concurrency": None,
}
)
assert _resolve_research_settings(get) == (8192, 90, 3)
def test_numeric_strings_still_parse():
get = _fake_get_setting(
{
"research_max_tokens": "4096",
"research_extraction_timeout_seconds": "45",
"research_extraction_concurrency": "2",
}
)
assert _resolve_research_settings(get) == (4096, 45, 2)
def test_missing_keys_keep_documented_defaults():
assert _resolve_research_settings(_fake_get_setting({})) == (8192, 90, 3)
def test_valid_ints_pass_through():
get = _fake_get_setting(
{
"research_max_tokens": 2048,
"research_extraction_timeout_seconds": 120,
"research_extraction_concurrency": 6,
}
)
assert _resolve_research_settings(get) == (2048, 120, 6)