Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions packages/cli/src/shared/agent-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,33 @@ export async function offerGithubAuth(runner: CloudRunner, explicitlyRequested?:
}

let ghCmd = "curl --proto '=https' -fsSL https://openrouter.ai/labs/spawn/shared/github-auth.sh | bash";
// Upload the token to a remote temp file so it never appears in `ps auxe`
// process listings. We use runner.uploadFile() (SCP) — the same proven
// pattern as uploadConfigFile(). A heredoc won't work here because all
// cloud runners wrap commands in `bash -c ${shellQuote(cmd)}`, and
// heredocs are not valid inside single-quoted `bash -c '...'` strings.
let remoteTokenPath = "";
if (githubToken) {
const tokenB64 = Buffer.from(githubToken).toString("base64");
ghCmd = `export GITHUB_TOKEN=$(printf '%s' ${shellQuote(tokenB64)} | base64 -d) && ${ghCmd}`;
const localTmpFile = join(getTmpDir(), `spawn_gh_token_${Date.now()}_${Math.random().toString(36).slice(2)}`);
remoteTokenPath = `/tmp/spawn_gh_token_${Date.now()}`;
writeFileSync(localTmpFile, githubToken, {
mode: 0o600,
});
const uploadResult = await asyncTryCatch(() => runner.uploadFile(localTmpFile, remoteTokenPath));
tryCatchIf(isOperationalError, () => unlinkSync(localTmpFile));
if (!uploadResult.ok) {
throw uploadResult.error;
Comment on lines +283 to +284
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Token upload failure throws unhandled exception, crashing the provisioning flow

In the old code, the GitHub token was embedded directly in ghCmd, so any failure was caught by the asyncTryCatchIf at line 290 and logged as a non-fatal warning. In the new code, a failed SCP upload at line 281 causes throw uploadResult.error at line 284, which propagates out of offerGithubAuth before reaching the graceful error handling at line 291. The caller at packages/cli/src/shared/orchestrate.ts:601 does await offerGithubAuth(cloud.runner, ...) with no try/catch, so a transient SCP failure during token upload will crash the entire VM provisioning flow — turning what was explicitly a non-fatal step ("GitHub CLI setup failed (non-fatal, continuing)") into a fatal one.

Prompt for agents
The throw at line 284 makes offerGithubAuth crash on SCP upload failure, whereas the old code (base64-in-command) was entirely non-fatal. The fix should handle the upload failure gracefully, consistent with the rest of the function. Two possible approaches:

1. Instead of throwing, log a warning and fall back to the non-token ghCmd (just the curl | bash without GITHUB_TOKEN). This keeps GitHub auth non-fatal while still attempting the more secure SCP approach.

2. Wrap the entire token-upload-and-run block in the same asyncTryCatchIf pattern used at line 290, so any failure in the upload path results in the same non-fatal warning.

The key constraint is that offerGithubAuth must never throw — its caller in orchestrate.ts:601 has no error handling, and all other steps in postInstall() (agent config, auto-update, security scan) are similarly designed to be non-fatal.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
ghCmd = `export GITHUB_TOKEN=$(cat ${shellQuote(remoteTokenPath)}) && rm -f ${shellQuote(remoteTokenPath)} && ${ghCmd}`;
}

logStep("Installing and authenticating GitHub CLI on the remote server...");
const ghSetup = await asyncTryCatchIf(isOperationalError, () => runner.runServer(ghCmd));
if (!ghSetup.ok) {
// Best-effort cleanup of remote token file if the command failed before rm ran
if (remoteTokenPath) {
await asyncTryCatchIf(isOperationalError, () => runner.runServer(`rm -f ${shellQuote(remoteTokenPath)}`));
}
logWarn("GitHub CLI setup failed (non-fatal, continuing)");
}

Expand Down
Loading