fix(native): keep the remote bootstrap retry for a closed listener

A remote Runner has no process evidence on the controller host, so startup
recovery cannot adopt a claimed remote startup that the drain suspended. Keep
the existing same-run bootstrap retry for remote targets and detach only local
Runner executions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
DottaandClaude Opus 5.5 committed 2026-11-06 14:22:15 -06:00
1 parent a35ec8b845
commit 5dc63f24fb
2 files changed
+11 -7

No files matched your search

+5 -3
View File
@@ -3292,11 +3292,13 @@ A graceful shutdown can also suspend a restart-recovery claim whose execution
has not yet reached provider dispatch. That execution has lost its authority:
it leaves the run running, its budget hold held and its workspace unmarked, and
the next controller adopts the same run. It does not settle the run as failed.
Shutdown closes the runtime API listener before it drains runs. A native
Shutdown closes the runtime API listener before it drains runs. A local Runner
execution that then needs the listener (for example, to give a new runner its
callback origin) detaches the same way instead of failing. It keeps its claim,
the drain suspends it, and the next controller adopts the run. A listener that
never started is still a startup failure.
the drain suspends it, and the next controller adopts the run. A remote Runner
execution has no process evidence on the controller host, so it keeps the
same-run bootstrap retry. A listener that never started is still a startup
failure.
Remote Runner processes use their authenticated connection lease as the bound
on disconnected lifetime. The controller's shorter connection-recovery budget
limits its own attempt; it must not terminate the remote owner while another
@@ -10707,11 +10707,13 @@ async function executePaperclipNativeSessionWithinScope(
error = identityRedactor.redact(error);
}
// A closed runtime API listener means this process is shutting down; it
// closes HTTP admission before it drains runs. Keep the claim for the
// drain to suspend, so the next controller adopts the run without spending
// a failure or a same-run retry.
// closes HTTP admission before it drains runs. For a local Runner, keep the
// claim for the drain to suspend: the next controller proves the Runner
// process identity and adopts the run without spending a failure or a
// same-run retry. A remote Runner has no process evidence on this host, so
// it keeps the bootstrap retry below.
if (nativeRunsDetachingForRestart.has(input.execution.binding.runId)
|| error instanceof LocalRuntimeApiListenerClosedError) {
|| (error instanceof LocalRuntimeApiListenerClosedError && input.runnerExecutionTarget?.kind !== "remote")) {
await leaseRenewal.stop().catch(() => undefined);
liveQuestions.close();
activeNativeSessions.delete(input.execution.binding.runId);