fix: harden subagent completion announce retries

This commit is contained in:
Peter Steinberger
2026-02-18 03:19:30 +01:00
parent d7c6136c1f
commit edf7d6af61
4 changed files with 252 additions and 25 deletions

View File

@@ -44,6 +44,8 @@ let listenerStop: (() => void) | null = null;
// Use var to avoid TDZ when init runs across circular imports during bootstrap.
var restoreAttempted = false;
const SUBAGENT_ANNOUNCE_TIMEOUT_MS = 120_000;
const MIN_ANNOUNCE_RETRY_DELAY_MS = 1_000;
const MAX_ANNOUNCE_RETRY_DELAY_MS = 8_000;
/**
* Maximum number of announce delivery attempts before giving up.
* Prevents infinite retry loops when `runSubagentAnnounceFlow` repeatedly
@@ -56,6 +58,12 @@ const MAX_ANNOUNCE_RETRY_COUNT = 3;
*/
const ANNOUNCE_EXPIRY_MS = 5 * 60_000; // 5 minutes
function resolveAnnounceRetryDelayMs(retryCount: number) {
const boundedRetryCount = Math.max(0, Math.min(retryCount, 10));
const baseDelay = MIN_ANNOUNCE_RETRY_DELAY_MS * 2 ** boundedRetryCount;
return Math.min(baseDelay, MAX_ANNOUNCE_RETRY_DELAY_MS);
}
function logAnnounceGiveUp(entry: SubagentRunRecord, reason: "retry-limit" | "expiry") {
const retryCount = entry.announceRetryCount ?? 0;
const endedAgoMs =
@@ -131,6 +139,22 @@ function resumeSubagentRun(runId: string) {
return;
}
const now = Date.now();
const delayMs = resolveAnnounceRetryDelayMs(entry.announceRetryCount ?? 0);
const earliestRetryAt = (entry.lastAnnounceRetryAt ?? 0) + delayMs;
if (
entry.expectsCompletionMessage === true &&
entry.lastAnnounceRetryAt &&
now < earliestRetryAt
) {
const waitMs = Math.max(1, earliestRetryAt - now);
setTimeout(() => {
resumeSubagentRun(runId);
}, waitMs).unref?.();
resumedRuns.add(runId);
return;
}
if (typeof entry.endedAt === "number" && entry.endedAt > 0) {
if (suppressAnnounceForSteerRestart(entry)) {
resumedRuns.add(runId);
@@ -317,6 +341,15 @@ function finalizeSubagentCleanup(runId: string, cleanup: "delete" | "keep", didA
entry.cleanupHandled = false;
resumedRuns.delete(runId);
persistSubagentRuns();
if (entry.expectsCompletionMessage !== true) {
return;
}
setTimeout(
() => {
resumeSubagentRun(runId);
},
resolveAnnounceRetryDelayMs(entry.announceRetryCount ?? 0),
).unref?.();
return;
}
if (cleanup === "delete") {