Skip to content

fix(selfhost): route the last two bare-Number() env knobs through parsePositiveIntEnv + preflight - #10098

Closed
RealDiligent wants to merge 1 commit into
JSONbored:mainfrom
RealDiligent:fix/critical-issue-env-knobs-parsePositiveIntEnv-10056
Closed

fix(selfhost): route the last two bare-Number() env knobs through parsePositiveIntEnv + preflight#10098
RealDiligent wants to merge 1 commit into
JSONbored:mainfrom
RealDiligent:fix/critical-issue-env-knobs-parsePositiveIntEnv-10056

Conversation

@RealDiligent

Copy link
Copy Markdown
Contributor

Summary

parsePositiveIntEnv is this repo's contract (#9157) for a numeric self-host env knob: a supplied non-finite/out-of-range value takes the fallback with a structured warn, and positiveInteger is its boot-time half. LOOPOVER_SHUTDOWN_LOCK_RELEASE_AFTER_MS (src/server.ts:1579) and OLLAMA_NUM_CTX (src/selfhost/ai.ts:742) were the only two self-host numeric knobs still on the bare-Number(process.env.X ?? "") form, and both fail exactly the two ways #9157 named:

  1. Silent feature disable. LOOPOVER_SHUTDOWN_LOCK_RELEASE_AFTER_MS=30s (or 2m, 30_000) → NaNNumber.isFinite false → the shutdown path silently takes "wait for the drain", i.e. the opt-in the operator just enabled does nothing, no log/metric/preflight error.
  2. A fractional value is accepted. Number("0.5") is finite & >0, so the shutdown Promise.race gets a 0.5 ms deadline every shutdown loses → the bulk releaseAllHeldLocksAtShutdown() fires on every shutdown (the behaviour the comment above it says was removed for causing double-actuation). ollamaNumCtx floors 0.5 to 0 — a disabled context window.

Fix

  • src/server.ts reads the knob via parsePositiveIntEnv("LOOPOVER_SHUTDOWN_LOCK_RELEASE_AFTER_MS", { min: 0, fallback: 0 }) and keeps the > 0 gate — unset ⇒ 0 ⇒ drain-first, byte-identical, while a malformed value now warns and falls back to 0 instead of doing the same thing unannounced.
  • ollamaNumCtx reads via parsePositiveIntEnv("OLLAMA_NUM_CTX", { min: 1, fallback: 32_768 }).
  • preflightEnv gains the two paired positiveInteger(...) entries (min 0 for the shutdown knob, matching GITHUB_CACHE_TTL_SECONDS; min 1 for OLLAMA_NUM_CTX), so a unit-suffix typo hard-fails boot.

Defaults, the shutdown drain-first ordering, parsePositiveIntEnv/positiveInteger themselves, the three existing positiveInteger calls, and ollamaContextOptions' provider gate are all unchanged. src/server.ts is in codecov.yml's ignore list (entrypoint), so its change is covered by the Docker boot smoke test, not unit coverage.

Tests

  • test/unit/selfhost-preflight.test.ts: preflightEnv reports a problem for LOOPOVER_SHUTDOWN_LOCK_RELEASE_AFTER_MS / OLLAMA_NUM_CTX on "30s", "30_000", "0.5", "-1" (plus "0" for OLLAMA_NUM_CTX), and ok: true for unset/""/valid integers ("0" accepted for the shutdown knob, "1" for the context window).
  • test/unit/selfhost-ai.test.ts: ollamaNumCtx() returns 32768 (not 0) for "0.5" and 65536 for "65536".

The "0.5" and malformed-preflight cases fail against the current bare-Number() code.

Closes #10056

@RealDiligent
RealDiligent requested a review from JSONbored as a code owner July 31, 2026 06:49
@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 31, 2026
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.79%. Comparing base (a7673e2) to head (35861e9).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main   #10098       +/-   ##
===========================================
- Coverage   91.88%   79.79%   -12.09%     
===========================================
  Files         930      284      -646     
  Lines      113827    59345    -54482     
  Branches    27466     8882    -18584     
===========================================
- Hits       104588    47354    -57234     
- Misses       7940    11695     +3755     
+ Partials     1299      296     -1003     
Flag Coverage Δ
backend 98.67% <100.00%> (+3.00%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/selfhost/ai.ts 98.38% <100.00%> (-0.01%) ⬇️
src/selfhost/preflight.ts 100.00% <100.00%> (ø)

... and 779 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 31, 2026
…sePositiveIntEnv + preflight

LOOPOVER_SHUTDOWN_LOCK_RELEASE_AFTER_MS (src/server.ts) and OLLAMA_NUM_CTX
(src/selfhost/ai.ts) were the only self-host numeric knobs still read with a bare
Number(process.env.X ?? ""), the exact form JSONbored#9157 replaced everywhere else. Both fail
the two ways that comment names: a unit-suffixed/separator value ("30s", "30_000")
NaN's and silently disables the opt-in with no signal, and a fractional value ("0.5")
is accepted — the shutdown deadline loses every race (bulk lock release fires on every
shutdown) and ollamaNumCtx floors 0.5 to 0.

Read both via parsePositiveIntEnv (server: { min: 0, fallback: 0 } to keep unset ⇒
wait-for-the-drain; ollama: { min: 1, fallback: 32_768 }), and add the paired
positiveInteger preflight entries so a malformed value hard-fails boot with a clear
message instead of only warning at use time. Defaults, the shutdown drain-first
ordering, and the ollama provider gate are unchanged.

Closes JSONbored#10056
@JSONbored
JSONbored force-pushed the fix/critical-issue-env-knobs-parsePositiveIntEnv-10056 branch from 35861e9 to dfbd081 Compare July 31, 2026 07:33
@RealDiligent

Copy link
Copy Markdown
Contributor Author

Superseded by #10108 — that PR includes the
pm run selfhost:env-reference regen that failed validate-code here (stale selfhost-env-reference.ts). Keeping one open PR for #10056.

JSONbored added a commit that referenced this pull request Jul 31, 2026
GitHub reports `mergeable_state: "unstable"` both for a failing non-required
check and for checks that are still running. The gate treated both as a
manual-review hold, so every PR whose CI had not finished got an enforcement
label: the executor denies merge and approve while it is present, and
merge-train.ts evicts a held sibling from the train entirely.

Section 1b's release condition also tested the same state, which made the label
a latch -- applied while CI was red, then not liftable because CI was red.
Observed on #10098, stuck until a human removed it by hand.

Neither escalation path could clear it: `releasedHolds` contains only
`guardrailHit`, and `guardrailEscalationCleared` requires green CI.

An unstable state no longer holds. It does not become mergeable either:
`wouldApprove` gains its own explicit `!unstableHolds` term (previously implied
by the hold), keyed on `unstableHolds` rather than the raw state so #9810's
ignored-check dismissal stays approvable, and `wouldMerge` already required
`clean`. Both label sites stop emitting -- the merge-autonomy fallback is
removed and the disposition ternary returns no label rather than falsely
claiming `ready-to-merge`.

Closes #10116
JSONbored added a commit that referenced this pull request Jul 31, 2026
…together

#10155 is flapping in production on beta.7: the label is
removed and re-added roughly every 90 seconds -- four cycles in eight minutes,
each pair a GitHub write and a subscriber notification. The audit trail shows
both operations in the SAME pass, two seconds apart.

Three post-plan transforms surface the manual-review hold -- the merge
circuit-breaker, the close circuit-breaker, and the close-audit holdout (#8831).
All three carried the identical idempotency check, which looks for an existing
ADD (`labelOp !== "remove"`) and therefore cannot see a planned REMOVE. When the
planner has already scheduled a release -- section 1b does, whenever nothing IT
knows about still wants a hold -- the transform appended an add next to that
remove, and the executor performed both.

Latent until #10116. `noManualReviewHoldWanted` used to include
`!mergeableStateUnstable`, which suppressed the release on exactly these PRs and
accidentally masked the missing case. Removing that term (correctly -- it was the
latch keeping #10098 stuck) let the release fire and exposed the contradiction.
#10116 did not create this; the label was previously a sticky latch, quieter but
strictly more broken.

The planner cannot fix it from its side. Section 1b's doc calls
noManualReviewHoldWanted "every reason that would ADD this label, in one place";
it is not, and cannot be, because these three run AFTER planning -- #8831 landed
long after that comment was written. Extending the planner's list would just be
one more thing to remember on the next transform.

So one shared withManualReviewHoldLabel() drops a planned release of the same
label before adding, and all three call sites use it. The contradiction becomes
unrepresentable where the add happens. The remove is dropped rather than the add
skipped: a transform only gets there by having just diverted a merge or a close,
so its hold is strictly newer than the release decided before that diversion.

Scoped to this label only -- dropping every label remove would silently defeat
the stale-disposition-label cleanup, which is its own mutation test.

gate.closeAuditHoldoutPct is 20 (the maximum) in the global Orb config, so about
one in five would-close PRs is eligible for the path that triggered this.

Closes #10164
JSONbored added a commit that referenced this pull request Jul 31, 2026
…together (#10167)

#10155 is flapping in production on beta.7: the label is
removed and re-added roughly every 90 seconds -- four cycles in eight minutes,
each pair a GitHub write and a subscriber notification. The audit trail shows
both operations in the SAME pass, two seconds apart.

Three post-plan transforms surface the manual-review hold -- the merge
circuit-breaker, the close circuit-breaker, and the close-audit holdout (#8831).
All three carried the identical idempotency check, which looks for an existing
ADD (`labelOp !== "remove"`) and therefore cannot see a planned REMOVE. When the
planner has already scheduled a release -- section 1b does, whenever nothing IT
knows about still wants a hold -- the transform appended an add next to that
remove, and the executor performed both.

Latent until #10116. `noManualReviewHoldWanted` used to include
`!mergeableStateUnstable`, which suppressed the release on exactly these PRs and
accidentally masked the missing case. Removing that term (correctly -- it was the
latch keeping #10098 stuck) let the release fire and exposed the contradiction.
#10116 did not create this; the label was previously a sticky latch, quieter but
strictly more broken.

The planner cannot fix it from its side. Section 1b's doc calls
noManualReviewHoldWanted "every reason that would ADD this label, in one place";
it is not, and cannot be, because these three run AFTER planning -- #8831 landed
long after that comment was written. Extending the planner's list would just be
one more thing to remember on the next transform.

So one shared withManualReviewHoldLabel() drops a planned release of the same
label before adding, and all three call sites use it. The contradiction becomes
unrepresentable where the add happens. The remove is dropped rather than the add
skipped: a transform only gets there by having just diverted a merge or a close,
so its hold is strictly newer than the release decided before that diversion.

Scoped to this label only -- dropping every label remove would silently defeat
the stale-disposition-label cleanup, which is its own mutation test.

gate.closeAuditHoldoutPct is 20 (the maximum) in the global Orb config, so about
one in five would-close PRs is eligible for the path that triggered this.

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

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

selfhost(config): route the two remaining bare-Number() env knobs through parsePositiveIntEnv and preflight

1 participant