fix(autopilot): give full-cycle dispatch a 30-minute timeout floor (#2852)

Dispatch timeout was derived as interval*2 with a 5-minute floor, tuned
for light per-interval work. A full autopilot cycle routinely needs more
than 10 minutes at common intervals, so healthy full cycles were killed
mid-run. Full-cycle dispatch now gets a 30-minute floor; lighter
dispatches keep the interval-derived budget.

Adds a regression test for the full-cycle floor.
This commit is contained in:
Sanchal Ranjan
2026-07-23 11:38:05 -07:00
committed by GitHub
parent 11659743a2
commit b98fae9b61
3 changed files with 30 additions and 2 deletions
+9
View File
@@ -0,0 +1,9 @@
export function resolveAutopilotDispatchTimeoutMs(
baseIntervalSeconds: number,
fullCycle: boolean,
): number {
const intervalDerivedTimeoutMs = Math.max(baseIntervalSeconds * 2 * 1000, 300_000);
return fullCycle
? Math.max(intervalDerivedTimeoutMs, 1_800_000)
: intervalDerivedTimeoutMs;
}
+5 -2
View File
@@ -38,6 +38,7 @@ import { logSelfUpgrade } from '../core/audit/self-upgrade-audit.ts';
import { detectInstallMethod } from './upgrade.ts';
import { evaluateQuietHours } from '../core/minions/quiet-hours.ts';
import { inspectLock } from '../core/db-lock.ts';
import { resolveAutopilotDispatchTimeoutMs } from './autopilot-timeout.ts';
/**
* v0.37.7.0 #1162 — classify autopilot reconnect-loop errors.
@@ -689,7 +690,7 @@ export async function runAutopilot(engine: BrainEngine, args: string[]) {
const queue = new MinionQueue(engine);
const slotMs = Math.floor(Date.now() / (baseInterval * 1000)) * baseInterval * 1000;
const slot = new Date(slotMs).toISOString();
const timeoutMs = Math.max(baseInterval * 2 * 1000, 300_000);
const timeoutMs = resolveAutopilotDispatchTimeoutMs(baseInterval, false);
// ── v0.40 D17: per-source freshness check ────────────────────
// Runs first; independent of score gate. Submits a 'sync' job per
@@ -931,7 +932,9 @@ export async function runAutopilot(engine: BrainEngine, args: string[]) {
const result = await dispatchPerSource(engine, queue, {
repoPath,
slot,
timeoutMs,
// Full cycles can outlive short daemon intervals. Keep lighter dispatches
// interval-derived while giving per-source consolidation enough time.
timeoutMs: resolveAutopilotDispatchTimeoutMs(baseInterval, true),
fanoutMax,
jsonMode,
});