From 5e691d3be254ba0a5d61318f9f21e52ffa926882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 30 Jun 2026 23:41:11 +0200 Subject: [PATCH] fix(insights): give the comprehensive briefing a longer generation budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comprehensive briefing aborted its provider call at the 120s budget on a full-history account — a heavier reasoning model spent longer than that before the first token, so the chain timed out, fell through to no fallback, and the briefing rendered its greeting with an empty body. Raise the comprehensive timeout to 180s so a slow first token no longer clips a generation that would otherwise complete; the per-user response timeout still overrides it for operators who need more, and the worker cap tracks this value. --- src/lib/ai/ai-budgets.ts | 14 +++++++++++++- .../jobs/__tests__/insight-pregenerate.test.ts | 18 +++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/lib/ai/ai-budgets.ts b/src/lib/ai/ai-budgets.ts index d97544066..6735cff37 100644 --- a/src/lib/ai/ai-budgets.ts +++ b/src/lib/ai/ai-budgets.ts @@ -61,8 +61,20 @@ export const AI_BUDGETS = { * generation runs off the request hot path (nightly warm + the on-demand * background warm), and the explicit regenerate is rate-limited, so the * wider ceiling is bounded. + * + * v1.25.12 — raised again to 180 s. The same "operation aborted due to + * timeout" hop (no upstream status) recurred at exactly the 120 s ceiling + * once the codex default slug moved to the heavier-reasoning `gpt-5.5` + * line: a wide account (full-history briefing, raw signals included) spends + * longer in the reasoning channel before the first visible token than the + * 120 s budget allowed, so the single-provider chain timed out with nothing + * to fall back to and the briefing rendered its greeting with no body. The + * per-user `aiResponseTimeoutSeconds` setting still overrides this default + * for operators on even slower backends; this only lifts the floor for + * accounts that never set it. The worker cap in `insight-pregenerate.ts` + * derives from this value plus fixed headroom, so it tracks automatically. */ - comprehensive: { temperature: 0.3, maxTokens: 1500, timeoutMs: 120_000 }, + comprehensive: { temperature: 0.3, maxTokens: 1500, timeoutMs: 180_000 }, /** * Per-metric status assessment cards — output is a single diff --git a/src/lib/jobs/__tests__/insight-pregenerate.test.ts b/src/lib/jobs/__tests__/insight-pregenerate.test.ts index 5dea6830d..219204e0b 100644 --- a/src/lib/jobs/__tests__/insight-pregenerate.test.ts +++ b/src/lib/jobs/__tests__/insight-pregenerate.test.ts @@ -304,10 +304,10 @@ describe("runInsightPregenerate — outcome tally", () => { observedSignal = opts.signal; setTimeout( () => resolve({ status: "generated", providerType: "x" }), - // v1.25.3 — above the default warm budget (~150 s = the 120 s + // v1.25.12 — above the default warm budget (~210 s = the 180 s // comprehensive provider budget + headroom) so the bounded budget // fires before this "slow" generation settles. - 200_000, + 260_000, ); }), ); @@ -323,7 +323,7 @@ describe("runInsightPregenerate — outcome tally", () => { statusGenerators, warmGenericMetrics, }); - await vi.advanceTimersByTimeAsync(160_000); + await vi.advanceTimersByTimeAsync(230_000); const result = await promise; // The timeout fired, aborted the still-running generation (so its @@ -798,10 +798,10 @@ describe("forceWarmUser — on-demand single-user warm (v1.8.7.1)", () => { // advance past it so the warm continues. setTimeout( () => resolve({ status: "generated", providerType: "x" }), - // v1.25.3 — above the default warm budget (~150 s = the 120 s + // v1.25.12 — above the default warm budget (~210 s = the 180 s // comprehensive provider budget + headroom) so the bounded budget // fires before this "slow" generation settles. - 200_000, + 260_000, ); }), ); @@ -818,7 +818,7 @@ describe("forceWarmUser — on-demand single-user warm (v1.8.7.1)", () => { warmGenericMetrics, }); // Advance past the comprehensive budget so withTimeout fires. - await vi.advanceTimersByTimeAsync(160_000); + await vi.advanceTimersByTimeAsync(230_000); const result = await promise; for (const g of statusGenerators) { @@ -916,10 +916,10 @@ describe("forceWarmUser — on-demand single-user warm (v1.8.7.1)", () => { observedSignal = opts.signal; setTimeout( () => resolve({ status: "generated", providerType: "x" }), - // v1.25.3 — above the default warm budget (~150 s = the 120 s + // v1.25.12 — above the default warm budget (~210 s = the 180 s // comprehensive provider budget + headroom) so the bounded budget // fires before this "slow" generation settles. - 200_000, + 260_000, ); }), ); @@ -935,7 +935,7 @@ describe("forceWarmUser — on-demand single-user warm (v1.8.7.1)", () => { statusGenerators, warmGenericMetrics, }); - await vi.advanceTimersByTimeAsync(160_000); + await vi.advanceTimersByTimeAsync(230_000); const result = await promise; // The timeout fired and aborted the still-running generation.