refactor(agents): unify guarded subagent lifecycle cleanup (#119637)

This commit is contained in:
Peter Steinberger
2026-08-05 09:00:50 -07:00
committed by GitHub
parent 9b2328dc37
commit 40a3bd2e10
7 changed files with 141 additions and 258 deletions
+14 -25
View File
@@ -340,17 +340,19 @@ async function killSubagentRun(params: {
targetState?: SubagentKillTargetState;
error?: string;
}> {
const markKilledBestEffort = () =>
markSubagentRunTerminatedBestEffort({
runId: params.entry.runId,
reason: "killed",
suppressTaskDelivery: params.suppressTaskDelivery,
});
const initialTargetState = resolveSubagentKillTargetState(params.entry);
if (initialTargetState) {
if (
params.entry.endedReason === SUBAGENT_ENDED_REASON_KILLED &&
params.entry.suppressAnnounceReason !== "steer-restart"
) {
markSubagentRunTerminatedBestEffort({
runId: params.entry.runId,
reason: "killed",
suppressTaskDelivery: params.suppressTaskDelivery,
});
markKilledBestEffort();
}
return { killed: false, targetState: initialTargetState };
}
@@ -399,11 +401,7 @@ async function killSubagentRun(params: {
params.entry.endedReason === SUBAGENT_ENDED_REASON_KILLED &&
params.entry.suppressAnnounceReason !== "steer-restart"
) {
markSubagentRunTerminatedBestEffort({
runId: params.entry.runId,
reason: "killed",
suppressTaskDelivery: params.suppressTaskDelivery,
});
markKilledBestEffort();
}
return { killed: false, sessionId, targetState: targetStateAfterRuntimeLoad };
}
@@ -535,11 +533,7 @@ async function killSubagentRun(params: {
targetState.task.status === "cancelled" &&
targetState.task.error === SUBAGENT_KILL_TASK_ERROR;
if (killedTarget) {
markSubagentRunTerminatedBestEffort({
runId: params.entry.runId,
reason: "killed",
suppressTaskDelivery: params.suppressTaskDelivery,
});
markKilledBestEffort();
} else {
try {
releaseSubagentRunKillClaim({
@@ -1166,8 +1160,8 @@ export async function steerControlledSubagentRun(params: {
// chat.abort remains the primary cleanup; exact session deletion is only
// the fallback when the accepted session row can be resolved.
}
if (!isAgentEventLifecycleGenerationCurrent(steerLifecycleGeneration)) {
await terminateAcceptedCollectorRun({
const terminateUnownedSteer = () =>
terminateAcceptedCollectorRun({
childSessionKey: params.entry.childSessionKey,
gatewayRunId: runId,
expectedSessionId: acceptedSessionEntry?.sessionId,
@@ -1175,6 +1169,8 @@ export async function steerControlledSubagentRun(params: {
callGateway: subagentControlDeps.callGateway,
timeoutMs: 10_000,
});
if (!isAgentEventLifecycleGenerationCurrent(steerLifecycleGeneration)) {
await terminateUnownedSteer();
clearSubagentRunSteerRestart(params.entry.runId, currentEntry);
return {
status: "error",
@@ -1197,14 +1193,7 @@ export async function steerControlledSubagentRun(params: {
task: params.message,
});
if (!replaced) {
await terminateAcceptedCollectorRun({
childSessionKey: params.entry.childSessionKey,
gatewayRunId: runId,
expectedSessionId: acceptedSessionEntry?.sessionId,
expectedLifecycleRevision: acceptedSessionEntry?.lifecycleRevision,
callGateway: subagentControlDeps.callGateway,
timeoutMs: 10_000,
});
await terminateUnownedSteer();
clearSubagentRunSteerRestart(params.entry.runId, currentEntry);
return {
status: "error",
@@ -73,60 +73,75 @@ export function createSubagentRegistryLifecycleCleanup(
entry.endedReason === SUBAGENT_ENDED_REASON_COMPLETE &&
entry.execution.outcome?.status === "ok";
const finalizeResumedAnnounceGiveUp = async (giveUpParams: {
const finalizeAnnounceGiveUp = async (giveUpParams: {
runId: string;
entry: SubagentRunRecord;
reason: "expiry" | "permanent_failure";
cleanup?: "delete" | "keep";
cleanupGeneration?: number;
retryCount?: number;
completedAt?: number;
}) => {
if (shouldSuspendPendingFinalDelivery(giveUpParams.entry)) {
const { runId, entry, reason, cleanup, cleanupGeneration, retryCount, completedAt } =
giveUpParams;
if (shouldSuspendPendingFinalDelivery(entry)) {
suspendPendingFinalDelivery({
runId: giveUpParams.runId,
entry: giveUpParams.entry,
reason: giveUpParams.reason,
error: getDeliveryLastError(giveUpParams.entry),
runId,
entry,
reason,
error: getDeliveryLastError(entry),
});
return;
}
const deliveryError = getDeliveryLastError(giveUpParams.entry) ?? giveUpParams.reason;
clearPendingFinalDelivery(giveUpParams.entry);
const failedDelivery = ensureDeliveryState(giveUpParams.entry);
const deliveryError = getDeliveryLastError(entry) ?? reason;
clearPendingFinalDelivery(entry);
const failedDelivery = ensureDeliveryState(entry);
failedDelivery.status = "failed";
failedDelivery.lastError = deliveryError;
if (retryCount != null) {
failedDelivery.attemptCount = retryCount;
failedDelivery.lastAttemptAt = completedAt ?? Date.now();
}
safeSetSubagentTaskDeliveryStatus({
entry: giveUpParams.entry,
entry,
deliveryStatus: "failed",
deliveryError,
});
safeMarkRequiredCompletionDeliveryBlocked({
entry: giveUpParams.entry,
entry,
reason: deliveryError,
});
giveUpParams.entry.wakeOnDescendantSettle = undefined;
const completion = ensureCompletionState(giveUpParams.entry);
entry.wakeOnDescendantSettle = undefined;
const completion = ensureCompletionState(entry);
completion.fallbackResultText = undefined;
completion.fallbackCapturedAt = undefined;
const shouldDeleteAttachments =
giveUpParams.entry.cleanup === "delete" || !giveUpParams.entry.retainAttachmentsOnKeep;
if (shouldDeleteAttachments) {
await safeRemoveAttachmentsDir(giveUpParams.entry);
if ((cleanup ?? entry.cleanup) === "delete" || !entry.retainAttachmentsOnKeep) {
await safeRemoveAttachmentsDir(entry);
}
const completionReason = resolveCleanupCompletionReason(giveUpParams.entry);
logAnnounceGiveUp(giveUpParams.entry, giveUpParams.reason);
if (
cleanupGeneration !== undefined &&
!isCleanupAttemptCurrent(runId, entry, cleanupGeneration)
) {
await retireSupersededCleanupIfNeeded(runId, entry, cleanupGeneration);
return;
}
const completionReason = resolveCleanupCompletionReason(entry);
logAnnounceGiveUp(entry, reason);
// Retry-limit / expiry give-up should not leave cleanup stuck behind the
// best-effort ended hook. Mark the run cleaned first, then fire the hook.
completeCleanupBookkeeping({
runId: giveUpParams.runId,
entry: giveUpParams.entry,
cleanup: giveUpParams.entry.cleanup,
completedAt: Date.now(),
runId,
entry,
cleanup: cleanup ?? entry.cleanup,
completedAt: completedAt ?? Date.now(),
});
if (!shouldSuppressSubagentRecoverySessionEffects(giveUpParams.entry)) {
if (!shouldSuppressSubagentRecoverySessionEffects(entry)) {
await emitCompletionEndedHookIfNeeded(
giveUpParams.entry,
entry,
completionReason,
() =>
isEndedHookOwnerCurrent(giveUpParams.runId, giveUpParams.entry) &&
!shouldSuppressSubagentRecoverySessionEffects(giveUpParams.entry),
isEndedHookOwnerCurrent(runId, entry) &&
!shouldSuppressSubagentRecoverySessionEffects(entry),
);
}
};
@@ -159,7 +174,7 @@ export function createSubagentRegistryLifecycleCleanup(
entry,
cleanupGeneration: cleanupGenerations.get(entry)!,
run: async () => {
await finalizeResumedAnnounceGiveUp({
await finalizeAnnounceGiveUp({
runId,
entry,
reason: "expiry",
@@ -326,64 +341,15 @@ export function createSubagentRegistryLifecycleCleanup(
}
if (deferredDecision.kind === "give-up") {
if (shouldSuspendPendingFinalDelivery(entry)) {
suspendPendingFinalDelivery({
runId,
entry,
reason: deferredDecision.reason,
error: getDeliveryLastError(entry),
});
return;
}
const deliveryError = getDeliveryLastError(entry) ?? deferredDecision.reason;
clearPendingFinalDelivery(entry);
const failedDelivery = ensureDeliveryState(entry);
failedDelivery.status = "failed";
failedDelivery.lastError = deliveryError;
if (deferredDecision.retryCount != null) {
failedDelivery.attemptCount = deferredDecision.retryCount;
failedDelivery.lastAttemptAt = now;
}
safeSetSubagentTaskDeliveryStatus({
entry,
deliveryStatus: "failed",
deliveryError,
});
safeMarkRequiredCompletionDeliveryBlocked({
entry,
reason: deliveryError,
});
entry.wakeOnDescendantSettle = undefined;
const completion = ensureCompletionState(entry);
completion.fallbackResultText = undefined;
completion.fallbackCapturedAt = undefined;
const shouldDeleteAttachments = cleanup === "delete" || !entry.retainAttachmentsOnKeep;
if (shouldDeleteAttachments) {
await safeRemoveAttachmentsDir(entry);
}
if (!isCleanupAttemptCurrent(runId, entry, cleanupGeneration)) {
await retireSupersededCleanupIfNeeded(runId, entry, cleanupGeneration);
return;
}
const completionReason = resolveCleanupCompletionReason(entry);
logAnnounceGiveUp(entry, deferredDecision.reason);
// Giving up on announce delivery is terminal for cleanup even if the
// best-effort hook is still resolving.
completeCleanupBookkeeping({
await finalizeAnnounceGiveUp({
runId,
entry,
reason: deferredDecision.reason,
cleanup,
cleanupGeneration,
retryCount: deferredDecision.retryCount,
completedAt: now,
});
if (!shouldSuppressSubagentRecoverySessionEffects(entry)) {
await emitCompletionEndedHookIfNeeded(
entry,
completionReason,
() =>
isEndedHookOwnerCurrent(runId, entry) &&
!shouldSuppressSubagentRecoverySessionEffects(entry),
);
}
return;
}
@@ -642,7 +608,7 @@ export function createSubagentRegistryLifecycleCleanup(
return {
completeCleanupBookkeeping,
finalizeResumedAnnounceGiveUp,
finalizeResumedAnnounceGiveUp: finalizeAnnounceGiveUp,
retireRunModeBundleMcpRuntime,
startSubagentAnnounceCleanupFlow,
};
+13 -22
View File
@@ -16,7 +16,7 @@ import {
} from "./subagent-registry-helpers.js";
import type { createSubagentRegistryLifecycleController } from "./subagent-registry-lifecycle.js";
import type { SubagentRunRecord } from "./subagent-registry.types.js";
import { isSessionLifecycleChangedGatewayError } from "./subagent-session-cleanup.js";
import { deleteSubagentSessionForCleanup } from "./subagent-session-cleanup.js";
import {
loadSubagentSessionEntry,
type SubagentSessionStoreCache,
@@ -327,27 +327,18 @@ export function createSubagentRegistryRestorer(config: {
if (!ownsCleanup()) {
return false;
}
try {
await deps().callGateway({
method: "sessions.delete",
params: {
key: entry.childSessionKey,
deleteTranscript: true,
expectedSessionId,
expectedLifecycleRevision,
emitLifecycleHooks: false,
},
timeoutMs: 10_000,
});
sessionDeleted = true;
return true;
} catch (cleanupError) {
if (isSessionLifecycleChangedGatewayError(cleanupError)) {
sessionOwnershipChanged = true;
return true;
}
throw cleanupError;
}
const outcome = await deleteSubagentSessionForCleanup({
callGateway: deps().callGateway,
childSessionKey: entry.childSessionKey,
expectedSessionId,
expectedLifecycleRevision,
onError: (cleanupError) => {
throw cleanupError;
},
});
sessionDeleted = outcome === "deleted";
sessionOwnershipChanged = outcome === "changed";
return outcome !== "failed";
},
{
shouldRetry: () => !launchTerminationConfirmed && ownsCleanup(),
+14 -19
View File
@@ -31,7 +31,7 @@ import type {
SubagentRunRecord,
} from "./subagent-registry.types.js";
import { isStaleUnendedSubagentRun } from "./subagent-run-liveness.js";
import { isSessionLifecycleChangedGatewayError } from "./subagent-session-cleanup.js";
import { deleteSubagentSessionForCleanup } from "./subagent-session-cleanup.js";
import {
loadSubagentSessionEntry,
resolveCompletionFromSessionEntry,
@@ -210,25 +210,20 @@ export function createSubagentRegistrySweeper(params: {
childSessionKey: string,
identity: FrozenSessionIdentity,
): Promise<"deleted" | "changed"> {
try {
await params.callGateway({
method: "sessions.delete",
params: {
key: childSessionKey,
deleteTranscript: true,
emitLifecycleHooks: false,
expectedSessionId: identity.sessionId,
expectedLifecycleRevision: identity.lifecycleRevision,
},
timeoutMs: 10_000,
});
return "deleted";
} catch (error) {
if (isSessionLifecycleChangedGatewayError(error)) {
return "changed";
}
throw error;
let failure: unknown;
const outcome = await deleteSubagentSessionForCleanup({
callGateway: params.callGateway,
childSessionKey,
expectedSessionId: identity.sessionId,
expectedLifecycleRevision: identity.lifecycleRevision,
onError: (error) => {
failure = error;
},
});
if (outcome === "failed") {
throw failure;
}
return outcome;
}
const sweptContext = (entry: SubagentRunRecord) => ({
+11 -10
View File
@@ -3,13 +3,13 @@
* the gateway and preserves lifecycle-hook behavior for session-mode spawns.
*/
import { SESSION_LIFECYCLE_CHANGED_ERROR_REASON } from "../config/sessions/lifecycle.js";
import type { callGateway as defaultCallGateway } from "../gateway/call.js";
import type { callGateway } from "../gateway/call.js";
import type { SpawnSubagentMode } from "./subagent-spawn.types.js";
type CallGateway = typeof defaultCallGateway;
type CallGateway = (options: Parameters<typeof callGateway>[0]) => Promise<unknown>;
type SubagentSessionCleanupOutcome = "deleted" | "changed" | "failed";
export function isSessionLifecycleChangedGatewayError(error: unknown): boolean {
function isSessionLifecycleChangedGatewayError(error: unknown): boolean {
if (!(error instanceof Error) || error.name !== "GatewayClientRequestError") {
return false;
}
@@ -28,8 +28,11 @@ export async function deleteSubagentSessionForCleanup(params: {
callGateway: CallGateway;
childSessionKey: string;
spawnMode?: SpawnSubagentMode;
emitLifecycleHooks?: boolean;
deleteTranscript?: boolean;
expectedSessionId?: string;
expectedLifecycleRevision?: string;
timeoutMs?: number;
onError?: (error: unknown) => void;
}): Promise<SubagentSessionCleanupOutcome> {
if (!params.expectedSessionId || !params.expectedLifecycleRevision) {
@@ -40,14 +43,12 @@ export async function deleteSubagentSessionForCleanup(params: {
method: "sessions.delete",
params: {
key: params.childSessionKey,
deleteTranscript: true,
emitLifecycleHooks: params.spawnMode === "session",
...(params.expectedSessionId ? { expectedSessionId: params.expectedSessionId } : {}),
...(params.expectedLifecycleRevision
? { expectedLifecycleRevision: params.expectedLifecycleRevision }
: {}),
deleteTranscript: params.deleteTranscript ?? true,
emitLifecycleHooks: params.emitLifecycleHooks ?? params.spawnMode === "session",
expectedSessionId: params.expectedSessionId,
expectedLifecycleRevision: params.expectedLifecycleRevision,
},
timeoutMs: 10_000,
timeoutMs: params.timeoutMs ?? 10_000,
});
return "deleted";
} catch (error) {
+17 -60
View File
@@ -1,27 +1,11 @@
import { promises as fs } from "node:fs";
import { SESSION_LIFECYCLE_CHANGED_ERROR_REASON } from "../config/sessions/lifecycle.js";
import type { callGateway } from "../gateway/call.js";
import { isFastTestRuntimeEnv } from "../infra/env.js";
import { deleteSubagentSessionForCleanup } from "./subagent-session-cleanup.js";
import { callSubagentGateway } from "./subagent-spawn-gateway.js";
const SUBAGENT_CONTROL_GATEWAY_TIMEOUT_MS = 60_000;
type GatewayCall = (options: Parameters<typeof callGateway>[0]) => Promise<unknown>;
type SessionCleanupOutcome = "deleted" | "changed" | "failed";
function isSessionLifecycleChangedGatewayError(error: unknown): boolean {
if (!(error instanceof Error) || error.name !== "GatewayClientRequestError") {
return false;
}
const requestError = error as Error & { gatewayCode?: unknown; details?: unknown };
const details = requestError.details;
return (
requestError.gatewayCode === "INVALID_REQUEST" &&
typeof details === "object" &&
details !== null &&
(details as { reason?: unknown }).reason === SESSION_LIFECYCLE_CHANGED_ERROR_REASON
);
}
function isMatchingAbortResponse(response: unknown, gatewayRunId: string): boolean {
if (!response || typeof response !== "object") {
return false;
@@ -65,35 +49,17 @@ type SessionCleanupOptions = {
timeoutMs?: number;
};
async function requestProvisionalSessionCleanup(
function requestProvisionalSessionCleanup(
childSessionKey: string,
options?: SessionCleanupOptions,
): Promise<SessionCleanupOutcome> {
if (!options?.expectedSessionId || !options.expectedLifecycleRevision) {
return "failed";
}
try {
await (options?.callGateway ?? callSubagentGateway)({
method: "sessions.delete",
params: {
key: childSessionKey,
emitLifecycleHooks: options?.emitLifecycleHooks === true,
deleteTranscript: options?.deleteTranscript === true,
...(options?.expectedSessionId ? { expectedSessionId: options.expectedSessionId } : {}),
...(options?.expectedLifecycleRevision
? { expectedLifecycleRevision: options.expectedLifecycleRevision }
: {}),
},
timeoutMs: options?.timeoutMs ?? SUBAGENT_CONTROL_GATEWAY_TIMEOUT_MS,
});
return "deleted";
} catch (error) {
if (isSessionLifecycleChangedGatewayError(error)) {
return "changed";
}
// Best-effort cleanup only.
return "failed";
}
) {
return deleteSubagentSessionForCleanup({
...options,
childSessionKey,
callGateway: options?.callGateway ?? callSubagentGateway,
deleteTranscript: options?.deleteTranscript === true,
timeoutMs: options?.timeoutMs ?? SUBAGENT_CONTROL_GATEWAY_TIMEOUT_MS,
});
}
export async function cleanupProvisionalSession(
@@ -125,30 +91,21 @@ export async function cleanupFailedSpawnBeforeAgentStart(params: {
expectedSessionId?: string;
expectedLifecycleRevision?: string;
}): Promise<{ attachmentsRemoved: boolean; sessionDeleted: boolean }> {
const { childSessionKey, attachmentAbsDir, waitForSessionDeletion, ...sessionCleanupOptions } =
params;
let attachmentsRemoved = true;
if (params.attachmentAbsDir) {
if (attachmentAbsDir) {
try {
await fs.rm(params.attachmentAbsDir, { recursive: true, force: true });
await fs.rm(attachmentAbsDir, { recursive: true, force: true });
} catch {
attachmentsRemoved = false;
}
}
const sessionCleanupOptions = {
emitLifecycleHooks: params.emitLifecycleHooks,
deleteTranscript: params.deleteTranscript,
expectedSessionId: params.expectedSessionId,
expectedLifecycleRevision: params.expectedLifecycleRevision,
};
if (params.waitForSessionDeletion) {
const sessionDeleted = await waitForProvisionalSessionDeletion(
params.childSessionKey,
sessionCleanupOptions,
);
return { attachmentsRemoved, sessionDeleted };
}
return {
attachmentsRemoved,
sessionDeleted: await cleanupProvisionalSession(params.childSessionKey, sessionCleanupOptions),
sessionDeleted: await (
waitForSessionDeletion ? waitForProvisionalSessionDeletion : cleanupProvisionalSession
)(childSessionKey, sessionCleanupOptions),
};
}
+24 -40
View File
@@ -202,6 +202,12 @@ export async function spawnSubagentDirect(
expectedSessionId: initialSession.entry?.sessionId,
expectedLifecycleRevision: initialSession.entry?.lifecycleRevision,
};
const cleanupCreatedSession = (emitLifecycleHooks = false) =>
cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks,
deleteTranscript: true,
...provisionalSessionIdentity,
});
const preparedSpawnContext = await prepareSubagentSessionContext({
cfg,
contextMode,
@@ -211,11 +217,7 @@ export async function spawnSubagentDirect(
childSessionKey,
});
if (preparedSpawnContext.status === "error") {
await cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks: false,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupCreatedSession();
return {
status: "error",
error: preparedSpawnContext.error,
@@ -229,11 +231,7 @@ export async function spawnSubagentDirect(
resolvedModel,
});
if (runtimeModelPersistError) {
await cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks: false,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupCreatedSession();
return {
status: "error",
error: runtimeModelPersistError,
@@ -258,11 +256,7 @@ export async function spawnSubagentDirect(
},
});
if (bindResult.status === "error") {
await cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks: false,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupCreatedSession();
return {
status: "error",
error: bindResult.error,
@@ -316,11 +310,7 @@ export async function spawnSubagentDirect(
mountPathHint,
});
if (materializedAttachments && materializedAttachments.status !== "ok") {
await cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks: threadBindingReady,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupCreatedSession(threadBindingReady);
return {
status: materializedAttachments.status,
error: materializedAttachments.error,
@@ -400,6 +390,15 @@ export async function spawnSubagentDirect(
spawnMode,
resolvedModelMetadata,
});
const cleanupFailedSpawn = (waitForSessionDeletion?: boolean) =>
cleanupFailedSpawnBeforeAgentStart({
childSessionKey,
attachmentAbsDir,
emitLifecycleHooks: threadBindingReady,
deleteTranscript: true,
...provisionalSessionIdentity,
waitForSessionDeletion,
});
type SubagentBackendState = { contextEnginePreparation?: SubagentSpawnPreparation };
const adapter: SpawnBackendAdapter<SubagentBackendState> = {
async initialize() {
@@ -427,13 +426,7 @@ export async function spawnSubagentDirect(
},
async cleanupOnFailure({ phase, state }) {
if (phase === "initialize") {
await cleanupFailedSpawnBeforeAgentStart({
childSessionKey,
attachmentAbsDir,
emitLifecycleHooks: threadBindingReady,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupFailedSpawn();
return;
}
await rollbackPreparedContextEngine(state?.contextEnginePreparation);
@@ -473,11 +466,7 @@ export async function spawnSubagentDirect(
}
emitLifecycleHooks = !endedHookEmitted;
}
await cleanupProvisionalSession(childSessionKey, {
emitLifecycleHooks,
deleteTranscript: true,
...provisionalSessionIdentity,
});
await cleanupCreatedSession(emitLifecycleHooks);
},
};
const pipelineResult = await runSpawnPipeline({
@@ -584,16 +573,11 @@ export async function spawnSubagentDirect(
const launchError = summarizeSpawnError(error);
const [contextRollback, sessionCleanup] = await Promise.allSettled([
rollbackPreparedContextEngine(pipelineResult.state.contextEnginePreparation),
cleanupFailedSpawnBeforeAgentStart({
childSessionKey,
attachmentAbsDir,
emitLifecycleHooks: threadBindingReady,
deleteTranscript: true,
...provisionalSessionIdentity,
cleanupFailedSpawn(
// A launch RPC can fail after acceptance. Keep the FIFO slot until
// deleting the child session proves no accepted run remains active.
waitForSessionDeletion: !launchTerminationConfirmed,
}),
!launchTerminationConfirmed,
),
]);
await retrySubagentCleanup(async () => {
settleFailedQueuedSubagentLaunch(childRunId, launchError);