From f1f8b53ddeff11ffbd6185e0d11ce5b8ddd0bc5b Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:35:49 -0700 Subject: [PATCH] fix: prepend IS_SANDBOX and PATH exports in buildFixScript (#2604) Fixes #2603 Agent: code-health Co-authored-by: B <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 --- packages/cli/package.json | 2 +- packages/cli/src/__tests__/cmd-fix.test.ts | 28 +++++++++++++++++-- packages/cli/src/commands/fix.ts | 31 ++++++++++++---------- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 58faa446..f3deee55 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.17.14", + "version": "0.17.15", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/__tests__/cmd-fix.test.ts b/packages/cli/src/__tests__/cmd-fix.test.ts index f64d669c..d573946c 100644 --- a/packages/cli/src/__tests__/cmd-fix.test.ts +++ b/packages/cli/src/__tests__/cmd-fix.test.ts @@ -49,6 +49,10 @@ describe("buildFixScript", () => { expect(script).toContain("set -eo pipefail"); expect(script).toContain("Re-injecting credentials"); + expect(script).toContain("IS_SANDBOX"); + expect(script).toContain(".npm-global/bin"); + expect(script).toContain(".bun/bin"); + expect(script).toContain(".cargo/bin"); expect(script).toContain("ANTHROPIC_API_KEY"); expect(script).toContain("~/.spawnrc"); expect(script).toContain("Re-installing agent"); @@ -104,7 +108,7 @@ describe("buildFixScript", () => { expect(script).toContain("Done!"); }); - it("handles agents without env vars", () => { + it("handles agents without env vars — still writes IS_SANDBOX and PATH", () => { const manifest = { ...mockManifest, agents: { @@ -119,10 +123,30 @@ describe("buildFixScript", () => { }; const script = buildFixScript(manifest, "claude"); - expect(script).not.toContain(".spawnrc"); + // IS_SANDBOX and PATH should always be written even without agent env vars + expect(script).toContain("IS_SANDBOX"); + expect(script).toContain(".npm-global/bin"); + expect(script).toContain("~/.spawnrc"); expect(script).toContain("Re-installing agent"); }); + it("prepends IS_SANDBOX and PATH before agent env vars (matches generateEnvConfig)", () => { + const script = buildFixScript(mockManifest, "claude"); + + const isSandboxIdx = script.indexOf("IS_SANDBOX"); + const pathIdx = script.indexOf(".npm-global/bin"); + const anthropicIdx = script.indexOf("ANTHROPIC_API_KEY"); + + // All three must be present + expect(isSandboxIdx).toBeGreaterThan(-1); + expect(pathIdx).toBeGreaterThan(-1); + expect(anthropicIdx).toBeGreaterThan(-1); + + // IS_SANDBOX and PATH must come before agent-specific env vars + expect(isSandboxIdx).toBeLessThan(anthropicIdx); + expect(pathIdx).toBeLessThan(anthropicIdx); + }); + it("throws for unknown agent", () => { expect(() => buildFixScript(mockManifest, "unknown-agent")).toThrow("Unknown agent: unknown-agent"); }); diff --git a/packages/cli/src/commands/fix.ts b/packages/cli/src/commands/fix.ts index dfd7f455..6f3ebdeb 100644 --- a/packages/cli/src/commands/fix.ts +++ b/packages/cli/src/commands/fix.ts @@ -42,23 +42,26 @@ export function buildFixScript(manifest: Manifest, agentKey: string): string { "", ]; - // Re-inject env vars into ~/.spawnrc + // Re-inject env vars into ~/.spawnrc (must match generateEnvConfig() output) const env = agentDef.env ?? {}; const envEntries = Object.entries(env); - if (envEntries.length > 0) { - lines.push("echo '==> Re-injecting credentials...'"); - // Write new .spawnrc atomically: write to .new then mv into place - lines.push("{"); - for (const [key, template] of envEntries) { - const value = resolveEnvTemplate(template); - lines.push(` printf 'export %s=%s\\n' ${shellSingleQuote(key)} ${shellSingleQuote(value)}`); - } - lines.push("} > ~/.spawnrc.new"); - lines.push("mv ~/.spawnrc.new ~/.spawnrc"); - lines.push("chmod 600 ~/.spawnrc"); - lines.push("echo ' Credentials updated in ~/.spawnrc'"); - lines.push(""); + lines.push("echo '==> Re-injecting credentials...'"); + // Write new .spawnrc atomically: write to .new then mv into place + lines.push("{"); + // Always prepend IS_SANDBOX and PATH — matches generateEnvConfig() in shared/agents.ts + lines.push(" printf 'export IS_SANDBOX=\\x271\\x27\\n'"); + lines.push( + " printf 'export PATH=\"$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$HOME/.cargo/bin:$HOME/.claude/local/bin:$PATH\"\\n'", + ); + for (const [key, template] of envEntries) { + const value = resolveEnvTemplate(template); + lines.push(` printf 'export %s=%s\\n' ${shellSingleQuote(key)} ${shellSingleQuote(value)}`); } + lines.push("} > ~/.spawnrc.new"); + lines.push("mv ~/.spawnrc.new ~/.spawnrc"); + lines.push("chmod 600 ~/.spawnrc"); + lines.push("echo ' Credentials updated in ~/.spawnrc'"); + lines.push(""); // Re-run the agent's install command to get the latest version const installCmd = agentDef.install;