fix(runner): separate remote artifact identity from launch path

This commit is contained in:
Dotta 2026-09-03 17:40:21 -05:00
parent 46eed91836
commit 73a146e076
4 changed files with 57 additions and 1 deletions

View File

@ -890,6 +890,9 @@ jobs:
OPENROUTER_API_KEY: ${{ matrix.credentialName == 'OPENROUTER_API_KEY' && secrets.OPENROUTER_API_KEY || '' }}
DAYTONA_API_KEY: ${{ matrix.environmentId == 'daytona' && secrets.DAYTONA_API_KEY || '' }}
PAPERCLIP_E2E_DAYTONA_IMAGE: ${{ needs.daytona_image.outputs.image }}
# Bind PRP identity to the exact build-once bytes that the server
# stages into Daytona instead of a separately built image binary.
PAPERCLIP_RUNNER_REMOTE_BINARY_PATH: ${{ github.workspace }}/packages/paperclip-runner/runner/target/debug/paperclip-runnerd
PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH: ${{ github.workspace }}/packages/paperclip-runner/provider-pack
PAPERCLIP_E2E_CAMPAIGN_ID: gha-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.executionId }}
run: pnpm test:e2e:runner -- --id "${{ matrix.executionId }}"

View File

@ -4618,11 +4618,53 @@ describe("runnerd provider runtime wiring", () => {
backendOptions.codexTransportFactory!();
expect(state.createTransport).toHaveBeenCalledWith(
expect.objectContaining({
runnerBinary: "/tmp/paperclip-runnerd",
environment: expect.objectContaining({
PAPERCLIP_WORKSPACE_CWD: remoteCwd,
}),
}),
);
expect(state.createTransport.mock.calls[0]![0].runnerBinary).not.toBe(
`${remoteCwd}/.paperclip-runtime/paperclip-runner/bin/paperclip-runnerd`,
);
});
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";
const remoteExecution = {
...execution,
binding: { ...execution.binding, runId: "run-remote-runner-artifact" },
workspace: { ...execution.workspace, cwd: "/host/paperclip-workspace" },
} as NativeExecutionInputV1;
await createRunnerdBackend({
db: leaseDb(remoteExecution),
execution: remoteExecution,
runnerInstanceId: "runner",
runnerExecutionTarget: {
kind: "remote",
transport: "ssh",
remoteCwd,
spec: {
host: "runner.internal",
port: 22,
username: "runner",
remoteWorkspacePath: remoteCwd,
remoteCwd,
privateKey: null,
knownHosts: null,
strictHostKeyChecking: true,
},
},
runnerRemoteBinaryPath: controllerArtifact,
});
state.createTransport.mockClear();
state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!();
expect(state.createTransport).toHaveBeenCalledWith(
expect.objectContaining({ runnerBinary: controllerArtifact }),
);
});
it.each([

View File

@ -5320,6 +5320,14 @@ async function createRunnerdBackendWithinSessionClaim(
const remoteBinary = remoteRuntimeRoot
? posix.join(remoteRuntimeRoot, "bin", "paperclip-runnerd")
: null;
// The transport hashes runnerBinary on the controller before an external
// launcher starts runnerd. Keep that artifact identity in the controller's
// filesystem; the remote launcher separately owns the sandbox command path.
// When an explicit remote artifact is configured, prepareRemoteRunner stages
// these exact bytes at remoteBinary before launch.
const controllerRunnerBinary = remoteTarget
? input.runnerRemoteBinaryPath?.trim() || resolvePaperclipRunnerBinary()
: resolvePaperclipRunnerBinary();
const explicitRemoteCodex = input.runnerRemoteCodexPath?.trim() || null;
const remoteCodexNpmSpec = input.runnerRemoteCodexNpmSpec?.trim() || null;
if (explicitRemoteCodex && remoteCodexNpmSpec) {
@ -6675,7 +6683,7 @@ async function createRunnerdBackendWithinSessionClaim(
stateDirectory: remoteStateDirectory,
})
: undefined,
runnerBinary: remoteBinary ?? resolvePaperclipRunnerBinary(),
runnerBinary: controllerRunnerBinary,
codexCommand: remoteCodexBinary ?? undefined,
sourceCodexHome: remoteTarget
? resolveSourceCodexHome(input.runnerEnvironment ?? process.env)

View File

@ -376,6 +376,9 @@ describe("public repository paid workflow security", () => {
expect(testJob).toContain(
"test -x packages/paperclip-runner/runner/target/debug/paperclip-runnerd",
);
expect(testJob).toContain(
"PAPERCLIP_RUNNER_REMOTE_BINARY_PATH: ${{ github.workspace }}/packages/paperclip-runner/runner/target/debug/paperclip-runnerd",
);
expect(testJob).toContain(".payload.runnerSourceRevision == $revision");
expect(workflow).toContain("Qualify local provider Node interpreter");
expect(testJob).toContain(