fix: stabilize subagent cleanup retries

This commit is contained in:
Peter Steinberger
2026-04-12 16:57:44 +01:00
parent ad826ea450
commit c146738996
4 changed files with 39 additions and 10 deletions

View File

@@ -78,4 +78,19 @@ describe("resolveDeferredCleanupDecision", () => {
expect(decision).toEqual({ kind: "retry", retryCount: 2, resumeDelayMs: 2_000 });
});
it("uses retry backoff for non-completion flows so cleanup can settle after announce failures", () => {
const decision = resolveDeferredCleanupDecision({
entry: makeEntry({ expectsCompletionMessage: false, announceRetryCount: 1 }),
now,
activeDescendantRuns: 0,
announceExpiryMs: 5 * 60_000,
announceCompletionHardExpiryMs: 30 * 60_000,
maxAnnounceRetryCount: 3,
deferDescendantDelayMs: 1_000,
resolveAnnounceRetryDelayMs: (retryCount) => retryCount * 1_000,
});
expect(decision).toEqual({ kind: "retry", retryCount: 2, resumeDelayMs: 2_000 });
});
});

View File

@@ -66,9 +66,6 @@ export function resolveDeferredCleanupDecision(params: {
return {
kind: "retry",
retryCount,
resumeDelayMs:
params.entry.expectsCompletionMessage === true
? params.resolveAnnounceRetryDelayMs(retryCount)
: undefined,
resumeDelayMs: params.resolveAnnounceRetryDelayMs(retryCount),
};
}

View File

@@ -173,6 +173,7 @@ export function createSessionsSpawnTool(
const mode = params.mode === "run" || params.mode === "session" ? params.mode : undefined;
const cleanup =
params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep";
const expectsCompletionMessage = params.expectsCompletionMessage !== false;
const sandbox = params.sandbox === "require" ? "require" : "inherit";
const streamTo = params.streamTo === "parent" ? "parent" : undefined;
const lightContext = params.lightContext === true;
@@ -289,7 +290,7 @@ export function createSessionsSpawnTool(
cleanup: trackedCleanup,
label: label || undefined,
runTimeoutSeconds,
expectsCompletionMessage: true,
expectsCompletionMessage,
spawnMode: trackedSpawnMode,
});
} catch (err) {
@@ -320,7 +321,7 @@ export function createSessionsSpawnTool(
cleanup,
sandbox,
lightContext,
expectsCompletionMessage: true,
expectsCompletionMessage,
attachments,
attachMountPath:
params.attachAs && typeof params.attachAs === "object"

View File

@@ -64,7 +64,15 @@ const {
targetArgs: string[];
watchMode: boolean;
};
resolveParallelFullSuiteConcurrency: (specCount: number, env?: NodeJS.ProcessEnv) => number;
resolveParallelFullSuiteConcurrency: (
specCount: number,
env?: NodeJS.ProcessEnv,
hostInfo?: {
cpuCount?: number;
loadAverage1m?: number;
totalMemoryBytes?: number;
},
) => number;
};
const VITEST_NODE_PREFIX = [
@@ -486,9 +494,17 @@ describe("test-projects args", () => {
it("uses a bounded local default for full-suite project parallelism", () => {
expect(
resolveParallelFullSuiteConcurrency(58, {
OPENCLAW_TEST_PROJECTS_LEAF_SHARDS: "1",
}),
resolveParallelFullSuiteConcurrency(
58,
{
OPENCLAW_TEST_PROJECTS_LEAF_SHARDS: "1",
},
{
cpuCount: 8,
loadAverage1m: 0,
totalMemoryBytes: 16 * 1024 ** 3,
},
),
).toBe(4);
});