Problem
Numeric environment variables are read with bare Number(...) and validated nowhere. A wrong unit or
type — the most common operator mistake — produces NaN, and Node coerces setTimeout(fn, NaN) and
setInterval(fn, NaN) to a 1 ms delay.
src/server.ts:1172:
const intervalMs = Number(process.env.CRON_INTERVAL_MS ?? 120_000);
delayToNextWallClockBoundaryMs (src/selfhost/cron-alignment.ts:10-13) is nowMs % intervalMs then
intervalMs - msIntoCycle, so NaN in → NaN out, and both the one-shot setTimeout and the follow-on
setInterval (src/server.ts:1193-1195) fire at 1 ms. intervalMs = 0 takes the same path
(x % 0 → NaN).
Trigger: an operator writes CRON_INTERVAL_MS=2m, =120s, =120_000, or =0 to "disable" the
cron. Result: ~1000 scheduled ticks per second instead of one per two minutes, each running the full
worker.scheduled() fan-out — sweep dispatch, ops-alerts, reconciliation, registry refresh. Queue
flooded with duplicate maintenance jobs, GitHub REST budget burned, AI spend follows.
Same unguarded shape elsewhere:
src/server.ts:958 — PORT NaN → binds a random port, breaking the compose healthcheck.
src/server.ts:729 — GITHUB_CACHE_TTL_SECONDS NaN → Math.max(0, NaN) = NaN → > 0 is false → the
GitHub response cache silently disables itself, quietly multiplying API usage.
preflightEnv (src/selfhost/preflight.ts:156-227) checks only REDIS_URL, the GitHub App pair,
DATABASE_URL, and secret strength. It validates no numeric var's type or range, so nothing stops boot.
Impact
A single-character config mistake produces a runaway that burns rate limit and AI spend, with no boot-time
error and no alert naming the cause. The cache case is worse in a way — it degrades silently and looks
like ordinary load.
Requirements
- Add a
positiveInteger(name, min, max) helper to preflight.ts and run CRON_INTERVAL_MS, PORT,
GITHUB_CACHE_TTL_SECONDS, and the *_MS admission/liveness knobs through it. Fail boot loudly on a
bad value rather than coercing.
- Clamp
intervalMs to a floor at the call site as defence in depth.
- Sweep for other bare
Number(process.env...) reads and bring them under the same helper.
- If
CRON_INTERVAL_MS=0 should mean "disable", implement that explicitly rather than letting it fall
into the NaN path.
Test Coverage Requirements
99%+ patch coverage, branch-counted; both arms per validated var, including the 0 and unit-suffix cases.
Links & Resources
maintainer-only — configuration safety.
Problem
Numeric environment variables are read with bare
Number(...)and validated nowhere. A wrong unit ortype — the most common operator mistake — produces
NaN, and Node coercessetTimeout(fn, NaN)andsetInterval(fn, NaN)to a 1 ms delay.src/server.ts:1172:delayToNextWallClockBoundaryMs(src/selfhost/cron-alignment.ts:10-13) isnowMs % intervalMsthenintervalMs - msIntoCycle, so NaN in → NaN out, and both the one-shotsetTimeoutand the follow-onsetInterval(src/server.ts:1193-1195) fire at 1 ms.intervalMs = 0takes the same path(
x % 0→ NaN).Trigger: an operator writes
CRON_INTERVAL_MS=2m,=120s,=120_000, or=0to "disable" thecron. Result: ~1000 scheduled ticks per second instead of one per two minutes, each running the full
worker.scheduled()fan-out — sweep dispatch, ops-alerts, reconciliation, registry refresh. Queueflooded with duplicate maintenance jobs, GitHub REST budget burned, AI spend follows.
Same unguarded shape elsewhere:
src/server.ts:958—PORTNaN → binds a random port, breaking the compose healthcheck.src/server.ts:729—GITHUB_CACHE_TTL_SECONDSNaN →Math.max(0, NaN)= NaN →> 0is false → theGitHub response cache silently disables itself, quietly multiplying API usage.
preflightEnv(src/selfhost/preflight.ts:156-227) checks onlyREDIS_URL, the GitHub App pair,DATABASE_URL, and secret strength. It validates no numeric var's type or range, so nothing stops boot.Impact
A single-character config mistake produces a runaway that burns rate limit and AI spend, with no boot-time
error and no alert naming the cause. The cache case is worse in a way — it degrades silently and looks
like ordinary load.
Requirements
positiveInteger(name, min, max)helper topreflight.tsand runCRON_INTERVAL_MS,PORT,GITHUB_CACHE_TTL_SECONDS, and the*_MSadmission/liveness knobs through it. Fail boot loudly on abad value rather than coercing.
intervalMsto a floor at the call site as defence in depth.Number(process.env...)reads and bring them under the same helper.CRON_INTERVAL_MS=0should mean "disable", implement that explicitly rather than letting it fallinto the NaN path.
Test Coverage Requirements
99%+ patch coverage, branch-counted; both arms per validated var, including the
0and unit-suffix cases.Links & Resources
src/server.ts~729, ~958, ~1172, ~1193-1195;src/selfhost/cron-alignment.ts~10-13;src/selfhost/preflight.ts~156-227wrong unit, out of range)
maintainer-only — configuration safety.