From 55a7a14d4e838a36a6d12a4ad78d12aa2383239e Mon Sep 17 00:00:00 2001 From: Ahmed Abushagur Date: Wed, 25 Mar 2026 16:49:49 -0700 Subject: [PATCH] fix: delegate ALL cloud credentials, not just the current cloud delegateCloudCredentials only copied the current cloud's config file (e.g. sprite.json when spawning on Sprite). Child VMs couldn't spawn on other clouds because their tokens weren't forwarded. Now iterates all known clouds and copies every credential file that exists locally, so the agent can spawn children on any cloud. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/shared/orchestrate.ts | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/shared/orchestrate.ts b/packages/cli/src/shared/orchestrate.ts index 8096a176..7fe20cec 100644 --- a/packages/cli/src/shared/orchestrate.ts +++ b/packages/cli/src/shared/orchestrate.ts @@ -137,31 +137,36 @@ export async function installSpawnCli(runner: CloudRunner): Promise { } /** Copy local cloud credentials to the remote VM for recursive spawning. */ -export async function delegateCloudCredentials(runner: CloudRunner, cloudName: string): Promise { +export async function delegateCloudCredentials(runner: CloudRunner, _cloudName: string): Promise { logStep("Delegating cloud credentials to VM..."); - // Validate cloudName to prevent command injection via crafted cloud names - if (!/^[a-z0-9-]+$/.test(cloudName)) { - logWarn(`Invalid cloud name for credential delegation: ${cloudName}`); - return; - } - const filesToDelegate: { localPath: string; remotePath: string; }[] = []; - // Current cloud's credentials - const cloudConfigPath = getSpawnCloudConfigPath(cloudName); - if (existsSync(cloudConfigPath)) { - filesToDelegate.push({ - localPath: cloudConfigPath, - remotePath: `~/.config/spawn/${cloudName}.json`, - }); + // Delegate ALL cloud credentials so the child VM can spawn on any cloud, + // not just the one the parent is running on. + const configDir = `${getUserHome()}/.config/spawn`; + const cloudNames = [ + "hetzner", + "digitalocean", + "aws", + "gcp", + "sprite", + ]; + for (const cloud of cloudNames) { + const cloudConfigPath = getSpawnCloudConfigPath(cloud); + if (existsSync(cloudConfigPath)) { + filesToDelegate.push({ + localPath: cloudConfigPath, + remotePath: `~/.config/spawn/${cloud}.json`, + }); + } } // OpenRouter credentials (always needed for child spawns) - const orConfigPath = `${getUserHome()}/.config/spawn/openrouter.json`; + const orConfigPath = `${configDir}/openrouter.json`; if (existsSync(orConfigPath)) { filesToDelegate.push({ localPath: orConfigPath,