Skip to content

XERK-523: route the runtime siblings' module-scope env knobs through a safe parser - #616

Merged
xerhab merged 1 commit into
mainfrom
XERK-523
Sep 1, 2026
Merged

XERK-523: route the runtime siblings' module-scope env knobs through a safe parser#616
xerhab merged 1 commit into
mainfrom
XERK-523

Conversation

@xerhab

@xerhab xerhab commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What & why

XERK-523 reports that a BARE int(os.environ.get(...)) at module scope crashes the whole agent at import on a junk value — one mistyped *_CONTEXT (or its LOCAL_MODEL_CONTEXT fallback) raises ValueError during import, before main(), and since the native agent runs the file as its managed foreground process, it crash-loops the host with the symptom pointing nowhere near the cause.

The headline line the ticket names (DSH_MODEL_CONTEXT in agent/hub-agent.py) was already converted to the safe _env_int helper by XERK-372 (commit 0e7f5fd, in main). This PR completes the ticket's second instruction — "grep for any other bare int(os.environ...) at module scope and convert them too" — for the remaining offenders in the stdlib-only runtime siblings that hub-agent.py imports on a dsh/qwen launch:

file knobs converted
agent/dsh_session.py DSH_ACK_TIMEOUT_SEC, DSH_CONNECT_TIMEOUT_SEC, DSH_LINE_MAX_BYTES, DSH_PROJECTION_POLL_SEC
agent/qwen_session.py QWEN_PROJECTION_POLL_SEC

A junk value in any of these raised ValueError (not the ImportError those launch paths degrade on), crashing the spawn — violating the XERK-372 invariant documented in .claude/rules/agent.md ("Every numeric env knob at module scope goes through _env_int/_env_float, NEVER a bare int(os.environ.get(...))").

How

  • These siblings are stdlib-only and deliberately can't reach hub-agent's private _env_num, so the safe parser lands in agent/runtime_tail.py — the XERK-528 "identical part once" module both siblings already import — as env_num/env_int/env_float, mirroring hub-agent's copy (fall back to a typed default + warn to stderr on junk; clamp on minimum/maximum; reject non-finite floats).
  • Each sibling's except ImportError branch gains default-returning stubs so a degraded standalone import (runtime_tail absent) still never raises.
  • The two function-scoped casts in agent/hooks/ask.py and agent/qwen/ask_mcp.py already carry their own try/except ValueError and are left as-is (they run at call time, can't crash the import).

No behavior change for any valid config value; the only change is junk → warn+default instead of crash.

How verified

  • New/updated tests (all unittest): agent/tests/test_runtime_tail.py (new, the shared helper); ModuleEnvKnobsTest in test_dsh_session.py / test_qwen_session.py (subprocess re-import with junk env → rc 0 + fallback); TestEnvNumHelpers.test_context_knobs_survive_a_junk_value in test_hub_agent.py (the ticket's exact DSH_MODEL_CONTEXT=lots / LOCAL_MODEL_CONTEXT=lots repro).
  • Adversarial QA pass (qa agent): PASS, no defects. Drove the real import-time behavior via subprocess reimport of all three files with hostile env ("", " ", 10m, "20", lots, nan, inf, 1_0 00, 26-digit): all exit 0 with the documented defaults; valid values still apply; the degraded except ImportError path stays importable (verified with runtime_tail removed from sys.path). Mutation-tested runtime_tail.py (neutered the non-finite guard and the min clamp) — each produced a distinct failure, so the tests have teeth. Suites: test_runtime_tail + test_dsh_session + test_qwen_session = 59 pass; TestEnvNumHelpers = 10 pass.

Scope: low-risk, bounded, agent-only; no hub/UI/container runtime surface.

…a safe parser

The DSH_MODEL_CONTEXT bare int() the ticket names was already converted to
_env_int by XERK-372 (commit 0e7f5fd, in main). This finishes the ticket's
"grep for any other bare int(os.environ...) at module scope and convert them"
step: the per-runtime sibling modules still parsed their own knobs with bare
casts at import time —

  agent/dsh_session.py:  DSH_ACK_TIMEOUT_SEC, DSH_CONNECT_TIMEOUT_SEC,
                         DSH_LINE_MAX_BYTES, DSH_PROJECTION_POLL_SEC
  agent/qwen_session.py: QWEN_PROJECTION_POLL_SEC

so a mistyped value crashes the dsh/qwen spawn that imports the module (a
ValueError, NOT the ImportError the launch paths degrade on), violating the
XERK-372 invariant documented in .claude/rules/agent.md.

These are stdlib-only siblings that can't reach hub-agent's private _env_num,
so the safe parser goes in runtime_tail.py — the XERK-528 "identical part once"
home both already import — as env_num/env_int/env_float, mirroring hub-agent's
copy (fall back + warn to stderr, clamp on minimum/maximum). Each sibling's
except-ImportError branch keeps a default-returning stub so the degraded
standalone import still never raises.

The function-scoped casts in hooks/ask.py and qwen/ask_mcp.py already carry
their own try/except ValueError and are left as-is.

Tests: a junk *_CONTEXT env no longer crashes the hub-agent import
(test_hub_agent TestEnvNumHelpers.test_context_knobs_survive_a_junk_value); the
sibling knobs fall back on junk via a subprocess re-import
(ModuleEnvKnobsTest in test_dsh_session / test_qwen_session); the shared helper
itself in the new test_runtime_tail.py.
@xerhab
xerhab merged commit 431532e into main Sep 1, 2026
4 checks passed
@xerhab
xerhab deleted the XERK-523 branch September 1, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant