From 607bc53ff38c9ab2e83495bf57247bc63541d0eb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Apr 2026 21:23:40 +0100 Subject: [PATCH] fix: import missing shell env keys --- CHANGELOG.md | 3 +++ src/infra/shell-env.test.ts | 35 +++++++++++++++++++++++++++++++++-- src/infra/shell-env.ts | 11 +++++------ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b13830baa5..88b25ac4eb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -632,6 +632,9 @@ Docs: https://docs.openclaw.ai ### Fixes +- Gateway/env: import each missing expected login-shell env var independently, + so an existing gateway token no longer prevents `env.shellEnv` from loading + plugin credentials such as `TWILIO_*` from `.profile`. Thanks @steipete. - macOS/Gateway pairing: silently accept same-host native app `metadata-upgrade` reconnects, so macOS patch-version changes update paired metadata instead of spamming security audit warnings and `pairing required` diff --git a/src/infra/shell-env.test.ts b/src/infra/shell-env.test.ts index 11f5e6db6b0..f5b87034dfe 100644 --- a/src/infra/shell-env.test.ts +++ b/src/infra/shell-env.test.ts @@ -137,8 +137,8 @@ describe("shell env fallback", () => { ).toBe(15000); }); - it("skips when already has an expected key", () => { - const env: NodeJS.ProcessEnv = { OPENAI_API_KEY: "set" }; + it("skips when already has all expected keys", () => { + const env: NodeJS.ProcessEnv = { OPENAI_API_KEY: "set", DISCORD_BOT_TOKEN: "set" }; const exec = vi.fn(() => Buffer.from("")); const res = runShellEnvFallback({ @@ -154,6 +154,37 @@ describe("shell env fallback", () => { expect(exec).not.toHaveBeenCalled(); }); + it("imports missing expected keys even when another expected key already exists", () => { + const env: NodeJS.ProcessEnv = { OPENCLAW_GATEWAY_TOKEN: "set" }; + const exec = vi.fn(() => + Buffer.from( + "OPENCLAW_GATEWAY_TOKEN=from-shell\0TWILIO_ACCOUNT_SID=AC123\0TWILIO_AUTH_TOKEN=secret\0TWILIO_FROM_NUMBER=+15550001234\0", + ), + ); + + const res = runShellEnvFallback({ + enabled: true, + env, + expectedKeys: [ + "OPENCLAW_GATEWAY_TOKEN", + "TWILIO_ACCOUNT_SID", + "TWILIO_AUTH_TOKEN", + "TWILIO_FROM_NUMBER", + ], + exec, + }); + + expect(res).toEqual({ + ok: true, + applied: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_FROM_NUMBER"], + }); + expect(env.OPENCLAW_GATEWAY_TOKEN).toBe("set"); + expect(env.TWILIO_ACCOUNT_SID).toBe("AC123"); + expect(env.TWILIO_AUTH_TOKEN).toBe("secret"); + expect(env.TWILIO_FROM_NUMBER).toBe("+15550001234"); + expect(exec).toHaveBeenCalledTimes(1); + }); + it("treats explicitly empty env vars as intentional overrides", () => { const env: NodeJS.ProcessEnv = { OPENAI_API_KEY: "" }; const exec = vi.fn(() => Buffer.from("OPENAI_API_KEY=from-shell\0")); diff --git a/src/infra/shell-env.ts b/src/infra/shell-env.ts index d0614a5349d..365bf703472 100644 --- a/src/infra/shell-env.ts +++ b/src/infra/shell-env.ts @@ -220,8 +220,10 @@ export function loadShellEnvFallback(opts: ShellEnvFallbackOptions): ShellEnvFal return { ok: true, applied: [], skippedReason: "disabled" }; } - const hasAnyKey = opts.expectedKeys.some((key) => hasExplicitEnvBinding(opts.env, key)); - if (hasAnyKey) { + const missingExpectedKeys = opts.expectedKeys.filter( + (key) => !hasExplicitEnvBinding(opts.env, key), + ); + if (missingExpectedKeys.length === 0) { lastAppliedKeys = []; return { ok: true, applied: [], skippedReason: "already-has-keys" }; } @@ -238,10 +240,7 @@ export function loadShellEnvFallback(opts: ShellEnvFallbackOptions): ShellEnvFal } const applied: string[] = []; - for (const key of opts.expectedKeys) { - if (hasExplicitEnvBinding(opts.env, key)) { - continue; - } + for (const key of missingExpectedKeys) { const value = probe.shellEnv.get(key); if (!value?.trim()) { continue;