fix(native): continue on a replacement when a retained Runner retires before claim (#19897)

This commit is contained in:
Nicky Leach authored and GitHub committed 2026-11-07 14:51:32 -08:00
1 parent 91b044858c
commit 94718ce168
5 files changed
+123 -47

No files matched your search

@@ -10811,40 +10811,38 @@ async function executePaperclipNativeSessionWithinScope(
const resolveWake = () => timeController("wake_binding", () => resolveCurrentWakeCommentsBinding(input.db, input.execution.binding));
const expectedCurrentWakeComments = await (executionContext ? executionContext.wait(resolveWake) : resolveWake());
let remoteWarmRecoveryPending = remoteWarmRecovery !== undefined;
const createExecutionBackend = () => createRunnerdBackend({
...input,
onDiagnosticLease: ownDiagnosticLease,
providerPackAdmissionSource,
recoveryPurpose: claimedStopCleanup ? "stop_cleanup" : "execution",
executionContext,
onAccountingEvent: producerUsage?.capture,
controllerPersistence: input.runnerExecutionTarget?.kind === "remote" ? {
bootId: controller.bootId,
runnerArtifact: restoredController ? { version: restoredController.runnerVersion, digest: restoredController.runnerDigest } : undefined,
} : undefined,
...(overlapGitHubPreparation ? { prepareRunnerEnvironment } : {}),
// Durable scope and prior-run verification use the controller's
// canonical workspace identity. createRunnerdBackend separately
// projects remoteCwd into the provider execution boundary.
execution: input.execution,
runnerInstanceId: effectiveRunnerInstanceId,
pluginTaskDescription,
retainedPluginTaskOwner: existingWarmSession ? pluginTaskSessionOwners.get(existingWarmSession) : undefined,
durableEnvironmentLeaseId: durableRunnerBinding?.environmentLeaseId,
remoteWarmRecovery: remoteWarmRecovery ? {
...remoteWarmRecovery,
revalidate: async () => {
if (remoteWarmRecoveryPending) await remoteWarmRecovery.revalidate(leaseOwner);
},
} : undefined,
trace,
toolTrace,
});
let runnerdBackend =
input.useRunnerd && input.backend === undefined
? await timeController("backend_setup", () => {
const create = () => createRunnerdBackend({
...input,
onDiagnosticLease: ownDiagnosticLease,
providerPackAdmissionSource,
recoveryPurpose: claimedStopCleanup ? "stop_cleanup" : "execution",
executionContext,
onAccountingEvent: producerUsage?.capture,
controllerPersistence: input.runnerExecutionTarget?.kind === "remote" ? {
bootId: controller.bootId,
runnerArtifact: restoredController ? { version: restoredController.runnerVersion, digest: restoredController.runnerDigest } : undefined,
} : undefined,
...(overlapGitHubPreparation ? { prepareRunnerEnvironment } : {}),
// Durable scope and prior-run verification use the controller's
// canonical workspace identity. createRunnerdBackend separately
// projects remoteCwd into the provider execution boundary.
execution: input.execution,
runnerInstanceId: effectiveRunnerInstanceId,
pluginTaskDescription,
retainedPluginTaskOwner: existingWarmSession ? pluginTaskSessionOwners.get(existingWarmSession) : undefined,
durableEnvironmentLeaseId: durableRunnerBinding?.environmentLeaseId,
remoteWarmRecovery: remoteWarmRecovery ? {
...remoteWarmRecovery,
revalidate: async () => {
if (remoteWarmRecoveryPending) await remoteWarmRecovery.revalidate(leaseOwner);
},
} : undefined,
trace,
toolTrace,
});
return executionContext ? executionContext.wait(create) : create();
})
? await timeController("backend_setup", () => executionContext ? executionContext.wait(createExecutionBackend) : createExecutionBackend())
: null;
if (remoteWarmRecovery) {
if (executionContext) await executionContext.wait(() => remoteWarmRecovery.revalidate(leaseOwner));
@@ -10877,7 +10875,7 @@ async function executePaperclipNativeSessionWithinScope(
durableEnvironmentLeaseId: durableRunnerBinding?.environmentLeaseId, trace, toolTrace });
}
}
const remoteCleanupLease = input.runnerExecutionTarget?.kind === "remote" &&
const readRemoteCleanupLease = async () => input.runnerExecutionTarget?.kind === "remote" &&
input.runnerExecutionTarget.transport === "sandbox" && input.runnerExecutionTarget.leaseId
? await input.db.select({ provider: environmentLeases.provider, providerLeaseId: environmentLeases.providerLeaseId })
.from(environmentLeases).where(and(
@@ -10885,6 +10883,26 @@ async function executePaperclipNativeSessionWithinScope(
eq(environmentLeases.id, input.runnerExecutionTarget.leaseId),
)).then(rows => rows[0])
: null;
let remoteCleanupLease = await readRemoteCleanupLease();
const continueAfterUnavailableReuse = async (error: unknown) => {
const target = input.runnerExecutionTarget;
if (!(error instanceof Error) || error.message !== "native_runner_reuse_unavailable" || !existingWarmSession
|| pluginTaskSessionOwners.get(existingWarmSession)?.retiredBeforeClaim() !== true
|| remoteWarmRecovery || warmConfigDigest === null || target?.kind !== "remote" || target.transport !== "sandbox"
|| !target.runnerTask || runnerdBackend === null) return false;
const retainedTaskId = target.sandboxLeaseAcquisition?.providerLeaseId;
await existingWarmSession.close({ reason: "retained Runner was unavailable for reuse" });
const inspect = () => target.runnerTask!({ kind: "describe" }, executionContext ? { executionContext } : undefined);
const description = environmentTaskResultSchema.parse(await (executionContext ? executionContext.wait(inspect) : inspect()));
if (description.kind !== "description" || description.taskId === retainedTaskId) return false;
existingWarmSession = undefined;
pluginTaskDescription = undefined;
await finishStartupContextForFallback();
persistedWarmSession = loadWarmNativeCheckpoint(input.execution, warmConfigDigest, target.kind);
runnerdBackend = await createExecutionBackend();
remoteCleanupLease = await readRemoteCleanupLease();
return true;
};
nativeSessionExecuteStartedAtMs = Date.now();
native = await trace.measure(
"native.session.execute",
@@ -10894,7 +10912,7 @@ async function executePaperclipNativeSessionWithinScope(
startedAtMs: nativeSessionExecuteStartedAtMs,
});
trace.activate(runnerSessionStartupScope);
const result = await trace.run(runnerSessionStartupScope, () =>
const runExecution = () =>
executeNativeSession({
resumeInterruptedTurn: input.restartRecovery?.kind === "resume_dead_runner",
getFreshSessionHandoff: input.getFreshSessionHandoff,
@@ -11274,8 +11292,14 @@ async function executePaperclipNativeSessionWithinScope(
);
}
},
}),
);
});
let result: Awaited<ReturnType<typeof runExecution>>;
try {
result = await trace.run(runnerSessionStartupScope, runExecution);
} catch (error) {
if (!await continueAfterUnavailableReuse(error)) throw error;
result = await trace.run(runnerSessionStartupScope, runExecution);
}
await trace.end(runnerSessionStartupScope, {
outcome:
result.terminal.runTerminalState === "succeeded" ? "ok" : "failed",