diff --git a/src/lib/credentialHealth/scheduler.ts b/src/lib/credentialHealth/scheduler.ts index fb5d7a6d31..f997df244d 100644 --- a/src/lib/credentialHealth/scheduler.ts +++ b/src/lib/credentialHealth/scheduler.ts @@ -200,7 +200,9 @@ export async function sweep(): Promise { state.sweepInProgress = true; try { - // Get all provider connections (API-key + OAuth) + // Get active provider connections only (API-key + OAuth). Disabled + // connections are excluded from routing and must not consume health-check + // concurrency or delay the scheduler with avoidable upstream timeouts. let connections: Array<{ id: string; provider: string; @@ -208,7 +210,7 @@ export async function sweep(): Promise { }>; try { - const raw = await getProviderConnections({}); + const raw = await getProviderConnections({ isActive: true }); connections = (Array.isArray(raw) ? raw : []).filter( (conn: any) => conn && conn.id && (conn.authType === "apikey" || conn.authType === "oauth") ) as Array<{ diff --git a/tests/unit/credential-health-active-connections-9180.test.ts b/tests/unit/credential-health-active-connections-9180.test.ts new file mode 100644 index 0000000000..095f895a72 --- /dev/null +++ b/tests/unit/credential-health-active-connections-9180.test.ts @@ -0,0 +1,53 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; + +const schedulerSource = fs.readFileSync( + new URL("../../src/lib/credentialHealth/scheduler.ts", import.meta.url), + "utf8" +); + +function getSweepConnectionSelection(): string { + const start = schedulerSource.indexOf("export async function sweep(): Promise"); + assert.notEqual(start, -1, "credential-health sweep must exist"); + + const end = schedulerSource.indexOf( + "\n if (connections.length === 0) return;", + start + ); + assert.notEqual(end, -1, "credential-health connection-selection block must exist"); + + return schedulerSource.slice(start, end); +} + +test("#9180 credential-health sweep queries active connections only", () => { + const selection = getSweepConnectionSelection(); + + assert.match( + selection, + /getProviderConnections\(\{\s*isActive:\s*true\s*\}\)/, + "the scheduler must request only active provider connections" + ); + + assert.doesNotMatch( + selection, + /getProviderConnections\(\{\s*\}\)/, + "the scheduler must not load disabled connections through an unfiltered query" + ); +}); + +test("#9180 active-only selection retains API-key and OAuth scope", () => { + const selection = getSweepConnectionSelection(); + + assert.match( + selection, + /conn\.authType === "apikey"/, + "API-key connections must remain eligible" + ); + + assert.match( + selection, + /conn\.authType === "oauth"/, + "OAuth connections must remain eligible" + ); +});