fix: stage the resolved server runner artifact for sandbox upgrades

Use the same controller-owned runner file for identity and remote staging, including packaged server vendor layouts.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-09 00:50:36 -05:00
parent 1ca638736a
commit b820a6d884
3 changed files with 54 additions and 3 deletions

View File

@ -84,6 +84,12 @@ overlay. This preserves its index and repository-local state. The legacy
outbound merge still saves working files to the host. A missing native sync
stamp on a pre-change lease does not make the host copy authoritative.
When an older sandbox image lacks a required runner capability, startup stages
the server-resolved runner artifact, including the vendored binary in packaged
server builds. The uploaded artifact and the controller's runner identity use
the same file. Replacement is atomic and preserves the previous launcher if
staging fails; it does not reset the task's working files or provider session.
Acceptance must resume representative pre-upgrade legacy and native tasks with
committed, staged, unstaged, and untracked work, verify their original paths and
usable continuation, and exercise their existing restore mechanism after a

View File

@ -6454,6 +6454,50 @@ describe("runnerd provider runtime wiring", () => {
).toBe(false);
});
it.each(["missing", "incompatible"])(
"stages the server-resolved artifact when the image runner is %s",
async (imageRunner) => {
const artifact = join(isolatedStateDirectory, "vendored-runnerd");
await writeFile(artifact, "server-owned runner bytes", { mode: 0o700 });
state.resolveRunnerBinary.mockReturnValueOnce(artifact);
const syncIn = vi.fn(async () => { throw new Error("observed-runner-upload"); });
const remoteExecute = vi.fn(async (command: { command: string; args?: string[] }) => {
const script = command.args?.[1] ?? "";
let stdout = "";
if (script.includes("command -v paperclip-runnerd")) {
stdout = imageRunner === "missing" ? "" : "/usr/local/bin/paperclip-runnerd\n";
} else if (command.args?.[0] === "--build-metadata") {
stdout = "{}"; // An incompatible image must fall back to the app artifact.
} else if (script === "uname -s; uname -m") {
const os = process.platform === "darwin" ? "Darwin" : "Linux";
const arch = process.arch === "x64" ? "x86_64" : "aarch64";
stdout = `${os}\n${arch}\n`;
}
return { exitCode: 0, signal: null, timedOut: false, stderr: "", stdout };
});
await createRunnerdBackend({
db: leaseDb(execution), execution, runnerInstanceId: "runner-vendored-artifact",
runnerIngressAuthorized: true,
runnerExecutionTarget: {
kind: "remote", transport: "sandbox", remoteCwd: "/workspace",
environmentId: "environment", leaseId: "lease", providerKey: "daytona",
effectiveCapabilities: { runnerWebSocketIngress: true },
runner: { execute: remoteExecute, syncIn },
} as never,
});
state.createTransport.mockClear();
state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!();
const transport = state.createTransport.mock.calls[0]![0] as RunnerTransportOptions & {
controlPlaneRegistration: (authority: unknown) => Promise<unknown>;
};
expect(transport.runnerBinary).toBe(artifact);
await expect(transport.controlPlaneRegistration({})).rejects.toThrow("observed-runner-upload");
expect(syncIn).toHaveBeenCalledWith([
expect.objectContaining({ files: [expect.objectContaining({ sourcePath: artifact, kind: "file", mode: 0o700 })] }),
]);
},
);
it("binds a remote launch to the configured controller-owned runner artifact", async () => {
const remoteCwd = "/home/daytona/paperclip-workspace";
const controllerArtifact = "/controller/artifacts/paperclip-runnerd";

View File

@ -40,7 +40,6 @@ import {
acpxRuntimeSessionDirectoryName,
createNativeSessionBackend,
createRunnerdCodexTransport,
defaultCapabilityRunnerdBinary,
executeNativeSession,
parseNativeExecutionInput,
parsePaperclipQuestionSet,
@ -6708,8 +6707,10 @@ async function createRunnerdBackendWithinSessionClaim(
}
}
if (!usedPreinstalledRunner) {
const sourceBinary =
explicitRemoteBinary ?? defaultCapabilityRunnerdBinary();
// Use the same server-resolved artifact the transport hashes. The
// package's development fallback does not resolve the vendored layout
// in a built server, even though its bin/paperclip-runnerd is present.
const sourceBinary = controllerRunnerBinary;
if (!existsSync(sourceBinary)) {
throw new Error("runner_remote_artifact_unavailable");
}