From c1ae76c6e57199d4bc9da0d6258700165c9eab79 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Thu, 28 May 2026 12:17:43 -0700 Subject: [PATCH] fix(exe-dev): translate SSH 'invalid format' stderr into actionable key-format message (PAPA-450) Co-Authored-By: Paperclip --- .../exe-dev/src/plugin.test.ts | 32 +++++++++++++++++++ .../sandbox-providers/exe-dev/src/plugin.ts | 8 +++++ 2 files changed, 40 insertions(+) diff --git a/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts b/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts index de4ef5c0996..dca69e39328 100644 --- a/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts +++ b/packages/plugins/sandbox-providers/exe-dev/src/plugin.test.ts @@ -346,6 +346,38 @@ describe("exe.dev sandbox provider plugin", () => { expect(String(fetchMock.mock.calls[1]?.[1]?.body ?? "")).toBe("rm --json 'paperclip-env-run'"); }); + it("surfaces invalid SSH key-format guidance during lease acquisition", async () => { + fetchMock.mockResolvedValueOnce( + new Response(JSON.stringify({ + vm_name: "paperclip-env-run", + ssh_dest: "paperclip-env-run.exe.xyz", + https_url: "https://paperclip-env-run.exe.xyz", + status: "running", + }), { status: 200 }), + ); + fetchMock.mockResolvedValueOnce(new Response("{}", { status: 200 })); + queueSpawnResult({ + code: 255, + stderr: 'Load key "/tmp/paperclip-exe-dev-ssh-abc/id_ed25519": invalid format\n', + }); + + await expect(plugin.definition.onEnvironmentAcquireLease?.({ + driverKey: "exe-dev", + companyId: "company-1", + environmentId: "env-1", + runId: "run-1", + config: { + apiKey: "api-key", + sshPrivateKey: "not-actually-a-key", + timeoutMs: 300000, + }, + })).rejects.toThrow( + "the configured SSH private key isn't an OpenSSH-format private key", + ); + + expect(String(fetchMock.mock.calls[1]?.[1]?.body ?? "")).toBe("rm --json 'paperclip-env-run'"); + }); + it("redacts sensitive lifecycle flags in API errors", async () => { fetchMock.mockResolvedValueOnce(new Response("upstream boom", { status: 500 })); diff --git a/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts b/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts index 5ec07d27f79..6ef21f2bdd3 100644 --- a/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts +++ b/packages/plugins/sandbox-providers/exe-dev/src/plugin.ts @@ -68,6 +68,7 @@ const SSH_SIGKILL_GRACE_MS = 250; const MAX_VM_RECORD_DEPTH = 4; const EXE_DEV_SSH_ONBOARDING_MARKER = "Please complete registration by running: ssh exe.dev"; const EXE_DEV_SSH_EMAIL_PROMPT = "Please enter your email address:"; +const EXE_DEV_SSH_INVALID_KEY_FORMAT = /Load key [^\n]*invalid format/i; // exe.dev's `--setup-script` runs at VM init as the unprivileged `exedev` user, which // has passwordless sudo. The Paperclip sandbox callback bridge is a Node script, so @@ -498,6 +499,13 @@ function formatSshFailure( ].join(" "); } + if (EXE_DEV_SSH_INVALID_KEY_FORMAT.test(combinedOutput)) { + return [ + `Failed to ${action} exe.dev VM ${vmName}: the configured SSH private key isn't an OpenSSH-format private key.`, + "Confirm the secret starts with `-----BEGIN … PRIVATE KEY-----` and isn't the `.pub` file or a PuTTY `.ppk` export.", + ].join(" "); + } + return `Failed to ${action} exe.dev VM ${vmName}: ${result.stderr.trim() || result.stdout.trim() || "unknown error"}`; }