fix(selfhost): validate AI reviewer-provider configuration and add a failure circuit breaker - #2604
fix(selfhost): validate AI reviewer-provider configuration and add a failure circuit breaker#2604JSONbored wants to merge 1 commit into
Conversation
…failure circuit breaker Closes #2540. Self-host's dual-AI review path resolved up to two reviewer slots from AI_PROVIDER with no distinctness check, so a config mistake (the same provider listed twice, e.g. a copy-paste artifact) silently collapsed "two independent reviewers reaching consensus" into "the same provider called twice" -- defeating dual-AI review and meaning one provider's outage/auth failure took down both slots at once. resolveAiReviewerPlan now throws a descriptive duplicate_ai_reviewer_provider error when the first two (distinctness-relevant) names match, mirroring assertNoLegacySharedAiEnv's existing fail-loud-at-boot pattern for other misconfigured env combinations. createChainAi also gained a per-provider circuit breaker: after 3 consecutive failures a provider is skipped (no network/CLI call at all) for a 5-minute cooldown, falling straight through to the next provider in the chain instead of repeating a doomed attempt on every single PR review during a sustained outage. In-process only, matching the module's existing global failure-streak pattern. Every provider circuit-open (no healthy fallback) throws a distinct all_ai_providers_circuit_open error instead of the misleading generic no_ai_providers default. New metrics (gittensory_ai_provider_circuit_total{provider,result}, gittensory_ai_review_inconclusive_total{mode,dual}) and two Prometheus alert rules give an operator visibility into a tripped provider and a spike in fail-closed HELD verdicts, which is often the correlated downstream symptom of the same outage.
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-02 20:54:33 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 7 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2604 +/- ##
=======================================
Coverage 96.02% 96.03%
=======================================
Files 233 233
Lines 26077 26105 +28
Branches 9474 9482 +8
=======================================
+ Hits 25041 25069 +28
Misses 425 425
Partials 611 611
🚀 New features to boost your workflow:
|
|
Closing as a duplicate — #2540 was already resolved by #2626 (merged), implemented independently in a parallel session. Confirmed via diff comparison: both PRs touch the exact same 5 files ( |
Summary
Closes #2540.
Self-host's dual-AI review path resolves up to two configured reviewer "slots" from
AI_PROVIDERwith no deduplication or distinctness check. A config mistake (the same provider listed twice — a copy-paste artifact) silently collapses "two independent reviewers reaching consensus" into "the same provider called twice in parallel," defeating the point of dual-AI review and meaning a single provider's outage/auth failure takes down both reviewer slots at once.Separately, there was no circuit breaker or failure-streak tracking per AI provider: every PR review independently retried a full attempt against a provider from a cold state, even seconds after that same provider failed the exact same way on the previous PR.
What changed
src/selfhost/ai.ts,resolveAiReviewerPlan): throws a descriptiveduplicate_ai_reviewer_providererror when the first two provider names (the ones that actually feed the two dual-review slots) are identical — a third duplicate further down an unused fallback chain is not this problem and is left alone. This mirrorsassertNoLegacySharedAiEnv's existing fail-loud-at-boot pattern for other misconfigured env combinations, at the exact same unguarded boot call site (resolveAiReviewerPlan(process.env)insrc/server.ts), so it crashes startup with a clear message the same way that pattern already does.createChainAi): after 3 consecutive failures, a provider is skipped entirely (no network/CLI call) for a 5-minute cooldown, falling straight through to the next provider in the chain. In-process only (module-levelMap), matching the existing globalaiConsecutiveFailuresstreak pattern rather than introducing a new persistence layer. When every provider in the chain is circuit-open, the chain throws a distinctall_ai_providers_circuit_openerror instead of the generic (and here misleading)no_ai_providersdefault.gittensory_ai_provider_circuit_total{provider,result}(tripped/skipped/recovered) andgittensory_ai_review_inconclusive_total{mode,dual}(a fail-closed HELD verdict — not an error, but a spike is the "dual-AI review is silently degrading" signal), plus two new Prometheus alert rules (GittensoryAiProviderCircuitOpen,GittensoryAiReviewInconclusiveSpike) in a newgittensory-airule group, matching this repo's existing alert-rule conventions (severity/summary/description/runbook).Correctness notes
createChainAi(...)call would do nothing), but it did collide with one pre-existing test that reused the same provider name for a failing and a working provider within one test. Fixed that test to use a distinct name, since its actual intent (the global health streak resetting on any success) is provider-name-agnostic.Tests
test/unit/selfhost-ai.test.ts: duplicate-slot validation (throws, case/whitespace-insensitive, does NOT throw for a 3rd-position duplicate or genuinely distinct providers); circuit breaker (stays closed below the streak threshold, opens and skips after 3 failures, distinct all-open error, per-provider streak reset on success, cooldown expiry re-attempts the provider, metrics for skipped/tripped/recovered).test/unit/ai-review.test.ts: the inconclusive-verdict metric fires on a fail-closed HOLD and stays silent on a clean pass.test/unit/selfhost-grafana-dashboard.test.ts: both new alert rules are present with their exact expressions.Validation
npm run typechecknpx vitest run test/unit/selfhost-ai.test.ts test/unit/ai-review.test.ts test/unit/selfhost-grafana-dashboard.test.tsnpm run test:changednpm run test:coverage(unsharded, full suite — 100% line and branch coverage on every changed line insrc/**percoverage/lcov.info, cross-checked programmatically against the diff)npm run db:migrations:check(no-op — no schema changes)node -e "require('yaml').parse(...)"to confirmprometheus/rules/alerts.ymlstill parses after the new groupnpm run test:ci(the full local gate, exit 0)npm audit --audit-level=moderategit diff --checkScope
site/,CNAME,**/lovable/**, orCHANGELOG.mdSafety