Skip to content

fix(startup): the Python-version floor check has no callers and never runs #13738

Closed

Description

@mrveiss

Discovered while root-causing #13727 (PR #13737).

startup_validator.py declares the platform's minimum Python version:

# autobot-backend/startup_validator.py:320
if sys.version_info < (3, 12):
    self.result.add_error(f"Python 3.12+ required, found {sys.version}")

That check has zero callers repo-wide. Neither validate_startup_dependencies nor StartupValidator is referenced from any .py, .sh, .yml, or .yaml outside the module that defines them:

$ grep -rn "startup_validator\|validate_startup_dependencies\|StartupValidator" \
    --include=*.py --include=*.sh --include=*.yml --include=*.yaml . \
    | grep -v node_modules | grep -v "^./autobot-backend/startup_validator.py"
(no output)

Why it matters

The floor is not decorative — code depends on it. llc/scheduler/base.py calls asyncio.Task.cancelling(), which is Python 3.11+ and has no fallback. On a sub-floor interpreter every LLC poll loop (LivenessMonitor, BudgetWatchdog, SessionCheckpointer, CommunityClusteringScheduler) dies after exactly one tick, with an AttributeError that is never retrieved because nothing awaits the task. Measured in #13727: 5 of 19 scheduler tests fail on 3.10 and pass on 3.11+.

So the failure mode is silent and total for background scheduling, and the one guard meant to catch it never runs. This is the same shape as the other unwired-code findings: the check exists, is correct, and is not connected to anything.

_validate_system_requirements also checks disk space, so more than the version gate is dark.

Fix

Wire the validator into startup (the lifespan path is the natural place) so a sub-floor interpreter or an out-of-space host fails loudly at boot instead of degrading silently. Per the repo's standing rule this is a wire-in, not a deletion — the check is right, it just has no caller.

Acceptance criteria

  • validate_startup_dependencies() (or StartupValidator) is invoked on a real startup path, with a test proving it runs.
  • A sub-floor Python version produces a loud, actionable startup error naming the required version.
  • Verified by deliberate failure: a simulated sub-floor sys.version_info makes startup fail, and the supported version still boots.

Related

Activity

  1. mrveiss commented on Aug 8, 2026

    @mrveiss
    OwnerAuthor

    Related: PR #13750 raises the floor this check enforces from 3.12 to 3.14, so once it is wired the number is correct. Wiring it is still this issue's job — the check has no callers either way. Context: epic #13743.

  2. mrveiss commented on Aug 9, 2026

    @mrveiss
    OwnerAuthor

    Delivered and merged: PR #13781, squashed as 57d4b30.

    The obvious fix breaks boot, which is worth recording. Before wiring anything I tried the lists validate_all travels with:

    FAIL src.unified_config          ModuleNotFoundError
    FAIL src.auth_middleware         ModuleNotFoundError
    FAIL src.security_layer          ModuleNotFoundError
    FAIL src.knowledge_base_factory  ModuleNotFoundError
    FAIL src.knowledge_base          ModuleNotFoundError
    FAIL aioredis                    ModuleNotFoundError
    

    All six are hard errors in validate_all. A check nobody ran is also a check nobody kept true: the src.* layout is long gone and aioredis has not been a dependency since the move to redis.asyncio. Calling validate_startup_dependencies() from the lifespan would have replaced a silent failure with a backend that refuses to start, reporting errors about the repo rather than the host.

    So the wiring is split by what each half is:

    The gate sits immediately after configure_logging(), before initialize_critical_services: on a sub-floor host an operator gets "Python 3.14+ required" rather than a Redis error, which is the diagnosis cost #13727 paid.

    • AC 1 — test_the_lifespan_calls_the_gate and test_the_gate_runs_before_any_service_is_touched, the latter driving the real lifespan context manager and asserting initialize_critical_services was never reached.
    • AC 2 + AC 3 — a 3.10 version_info raises and the message names 3.14, so an operator learns what to install; the floor itself passes. The disk gate gets the same treatment: zero free bytes raises, 3 GB warns and boots.
    startup_validator_wiring_test.py                     26 passed
    initialization/ + startup_validator_wiring_test.py   50 passed
    

    Three mutation dimensions: reverting both files → collection error; reverting only lifespan.py → both wiring tests red; restoring aioredis/src.unified_config → both parametrised list tests red. That last one is the point of those tests — they are what stops the lists drifting back into the state that made this check useless.

    The floor constant needed no change: it already read 3.14, matching CI, .python-version, mypy, both Dockerfiles and the Ansible roles.

    Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions