fix(quota-share): keep #5923 silent-stop bookkeeping on the no-targets early exit

The #11371 slot-release replaced (instead of augmenting) the recordComboFailure
call on the no-executable-targets path, freezing the pin auto-clear counter.
Restore both effects side by side + regression guard in combo-routing-engine.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
oyi77
2026-08-25 04:07:38 -03:00
committed by Markus Hartung
parent ad2e8f600e
commit dc39a7bcfd
2 changed files with 41 additions and 0 deletions

View File

@@ -955,6 +955,7 @@ async function handleComboChatInner({
// Surface a recovery hint + auto-clear the session pin after enough consecutive
// no-target failures (silent-stop fix). Threshold of 3 prevents a one-off account
// wipe from destroying the prompt-cache pin benefit on the next request.
recordComboFailure(effectiveSessionId, combo.name);
// #11371: same early-exit release as the pinned-turn path above.
targetResolution.quotaShareRelease?.();
return errorResponseWithComboDiagnostics(

View File

@@ -16,6 +16,9 @@ const {
handleComboChat,
} = await import("../../open-sse/services/combo.ts");
const { resolveComboTargets } = await import("../../open-sse/services/combo/comboStructure.ts");
const { getComboFailureCount, __resetComboFailureTrackerForTests } = await import(
"../../open-sse/services/combo/failureTracker.ts"
);
const { applyPromptCacheAffinity } =
await import("../../open-sse/services/combo/promptCacheAffinity.ts");
const { resolveReasoningBufferedMaxTokens } =
@@ -1327,6 +1330,43 @@ test("handleComboChat returns 404 model_not_found when a combo has no executable
assert.match(payload.error.message, /Combo has no executable targets/);
});
test("#11408 guard: no-executable-targets early exit still records the combo failure (silent-stop counter, #5923)", async () => {
__resetComboFailureTrackerForTests();
const result = await handleComboChat({
body: {},
combo: {
name: "guard-empty-11408",
strategy: "priority",
models: [],
context_cache_protection: true,
},
handleSingleModel: async () => {
throw new Error("handleSingleModel should not run for empty combos");
},
isModelAvailable: async () => true,
log: createLog(),
settings: {
comboDefaults: {
maxRetries: 0,
retryDelayMs: 1,
},
},
relayOptions: { sessionId: "sess-guard-11408" } as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 404);
// The quota-share slot release must not replace the #5923 silent-stop
// bookkeeping: the consecutive-failure counter must still advance so the
// session pin auto-clears after COMBO_FAILURE_THRESHOLD no-target failures.
assert.equal(
getComboFailureCount("sess-guard-11408", "guard-empty-11408"),
1,
"recordComboFailure must run on the no-executable-targets early exit"
);
});
test("handleComboChat round-robin returns 404 when no models are configured", async () => {
const result = await handleComboChat({
body: {},