fix(minions): give facts-absorb the 10-min handler-timeout default (#3207)

Add 'facts-absorb' to HANDLER_DEFAULT_TIMEOUT_MS (same one-page-one-LLM-call
shape as chronicle_extract) and drop the hardcoded 180s timeout_ms on the
durable backstop submit so the submit-time map stamp applies — an explicit
opts.timeout_ms always wins, which would have neutralized a map-only fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-23 11:08:00 -07:00
co-authored by Claude Fable 5
parent e79b8d5780
commit 1afae80f7a
3 changed files with 39 additions and 1 deletions
+3 -1
View File
@@ -191,7 +191,9 @@ export async function runFactsBackstop(
// identical writes (idempotent ON CONFLICT returns existing row).
idempotency_key: `facts-absorb:${ctx.sourceId}:${parsedPage.slug}:${contentHash}`,
max_attempts: 3,
timeout_ms: 180_000,
// No explicit timeout_ms: let MinionQueue.add stamp the
// handler-timeouts.ts default (#3207 — a hardcoded 180s here
// overrode the map and wall-clock-killed slow-gateway absorbs).
},
);
return { mode: 'queue', enqueued: true, queueDepth: 0 };
+3
View File
@@ -43,6 +43,9 @@ export const HANDLER_DEFAULT_TIMEOUT_MS: Readonly<Record<string, number>> = {
// few writes. Generous 10-min budget (vs the tight null-default) covers a
// slow gateway without the 30-min loop budget.
chronicle_extract: TEN_MIN_MS,
// #3207 — facts absorb: same one-page-one-LLM-call shape as
// chronicle_extract; a slow gateway must not wall-clock-kill it.
'facts-absorb': TEN_MIN_MS,
// Per-page contextual reindex jobs process chunks sequentially with one
// rate-leased LLM synopsis call per chunk; large transcript pages need more
// than the standard 30-min long-job budget.
+33
View File
@@ -301,3 +301,36 @@ describe('runFactsBackstop — stub guard routing (v0.34.5)', () => {
}
});
});
describe('#3207 — durable facts-absorb submit uses the handler-timeout map', () => {
test('defaultTimeoutMsFor stamps facts-absorb with the 10-min budget (matches chronicle_extract)', async () => {
const { defaultTimeoutMsFor } = await import('../src/core/minions/handler-timeouts.ts');
expect(defaultTimeoutMsFor('facts-absorb')).toBe(10 * 60 * 1000);
expect(defaultTimeoutMsFor('facts-absorb')).toBe(defaultTimeoutMsFor('chronicle_extract'));
});
test('short-lived CLI durable submit inherits the map default (no hardcoded 180s override)', async () => {
const { markShortLivedCliProcess, __resetShortLivedCliForTests } =
await import('../src/core/facts/cli-process-mode.ts');
markShortLivedCliProcess();
try {
const page = meetingPage();
const r = await runFactsBackstop(page, makeCtx());
expect(r.mode).toBe('queue');
if (r.mode === 'queue') expect(r.enqueued).toBe(true);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rows = await (engine as any).db.query(
`SELECT timeout_ms FROM minion_jobs WHERE name = 'facts-absorb' AND data->>'slug' = $1`,
[page.slug],
);
expect(rows.rows.length).toBe(1);
// Submit-time stamp comes from HANDLER_DEFAULT_TIMEOUT_MS, not the old
// hardcoded 180_000 that overrode any map-only fix (the map's contract:
// "An explicit opts.timeout_ms always wins").
expect(Number(rows.rows[0].timeout_ms)).toBe(10 * 60 * 1000);
} finally {
__resetShortLivedCliForTests();
}
});
});