From 6da2d6ac5a0d7d07124fbe6be65115df3f8fc0d5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 16 Jun 2026 04:58:47 +0200 Subject: [PATCH] fix(crabbox): bootstrap absolute macOS env pnpm --- scripts/crabbox-wrapper.mjs | 17 ++--- test/scripts/crabbox-wrapper.test.ts | 104 ++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/scripts/crabbox-wrapper.mjs b/scripts/crabbox-wrapper.mjs index f6c4dcb2492..b9266db8f14 100755 --- a/scripts/crabbox-wrapper.mjs +++ b/scripts/crabbox-wrapper.mjs @@ -932,6 +932,7 @@ function stripShellExecutionPrefixes(wordsInput, options = {}) { function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) { const originalWords = [...words]; const envCommand = words.shift() ?? ""; + const canShimThisEnv = canShimIgnoreEnvironment && isSupportedSystemEnvCommand(envCommand); let ignoresEnvironment = false; for (;;) { const word = words[0] ?? ""; @@ -976,7 +977,7 @@ function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) return words.length > 0; } if (word === "-i" || word === "--ignore-environment") { - if (!canShimIgnoreEnvironment || envCommand.includes("/")) { + if (!canShimThisEnv) { words.splice(0, words.length, ...originalWords); return false; } @@ -997,7 +998,7 @@ function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) } if (word.startsWith("-") && word !== "-") { if (word.includes("i")) { - if (!canShimIgnoreEnvironment || envCommand.includes("/")) { + if (!canShimThisEnv) { words.splice(0, words.length, ...originalWords); return false; } @@ -1006,7 +1007,7 @@ function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) words.shift(); continue; } - if (ignoresEnvironment && (!canShimIgnoreEnvironment || envCommand.includes("/"))) { + if (ignoresEnvironment && !canShimThisEnv) { words.splice(0, words.length, ...originalWords); return false; } @@ -1014,6 +1015,10 @@ function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) } } +function isSupportedSystemEnvCommand(command) { + return command === "env" || command === "/usr/bin/env"; +} + function shellWordBasename(word) { return (word ?? "").split("/").pop() ?? ""; } @@ -1738,11 +1743,7 @@ function remoteAwsMacosJsBootstrap({ packageManager = false } = {}) { } function scopedAwsMacosEnvCommand(commandArgs) { - if ( - commandArgs.length <= 1 || - shellWordBasename(commandArgs[0]) !== "env" || - commandArgs[0].includes("/") - ) { + if (commandArgs.length <= 1 || !isSupportedSystemEnvCommand(commandArgs[0])) { return null; } diff --git a/test/scripts/crabbox-wrapper.test.ts b/test/scripts/crabbox-wrapper.test.ts index 932442ccf4d..cf9e94ae292 100644 --- a/test/scripts/crabbox-wrapper.test.ts +++ b/test/scripts/crabbox-wrapper.test.ts @@ -826,7 +826,7 @@ describe.concurrent("scripts/crabbox-wrapper", () => { expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js"); expect(remoteCommand).toContain('corepack enable --install-directory "$PNPM_HOME"'); expect(remoteCommand).toContain("pnpm --version >&2"); - expectGroupedShellCommand(remoteCommand, "/usr/bin/env pnpm --version"); + expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env pnpm --version"); }); it("bootstraps Corepack for raw AWS macOS env option pnpm commands", () => { @@ -895,7 +895,7 @@ describe.concurrent("scripts/crabbox-wrapper", () => { ); }); - it("does not bootstrap absolute env ignore-environment commands it cannot preserve", () => { + it("bootstraps Corepack for raw AWS macOS absolute env ignore-environment commands", () => { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", [ @@ -913,6 +913,62 @@ describe.concurrent("scripts/crabbox-wrapper", () => { ], ); + const output = parseFakeCrabboxOutput(result); + const remoteCommand = normalizeShellLineEndings(output.args.at(-1) ?? ""); + expect(result.status).toBe(0); + expect(output.args).toContain("--shell"); + expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js"); + expect(remoteCommand).toContain("pnpm --version >&2"); + expectGroupedShellCommand( + remoteCommand, + "openclaw_crabbox_env -i PATH=/usr/bin:/bin pnpm --version", + ); + }); + + it("injects the bootstrapped PATH for raw AWS macOS absolute env -i commands", () => { + const result = runWrapper( + "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", + [ + "run", + "--provider", + "aws", + "--target", + "macos", + "--", + "/usr/bin/env", + "-i", + "pnpm", + "--version", + ], + ); + + const output = parseFakeCrabboxOutput(result); + const remoteCommand = normalizeShellLineEndings(output.args.at(-1) ?? ""); + expect(result.status).toBe(0); + expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js"); + expect(remoteCommand).toContain( + 'if [ "$openclaw_env_ignore" = "1" ] && [ "$openclaw_env_path_seen" = "0" ]; then openclaw_env_args+=("PATH=$PATH"); fi;', + ); + expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env -i pnpm --version"); + }); + + it("does not rewrite custom env executables for raw AWS macOS ignore-environment commands", () => { + const result = runWrapper( + "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", + [ + "run", + "--provider", + "aws", + "--target", + "macos", + "--", + "./tools/env", + "-i", + "pnpm", + "--version", + ], + ); + const output = parseFakeCrabboxOutput(result); const remoteCommand = normalizeShellLineEndings(output.args.at(-1) ?? ""); expect(result.status).toBe(0); @@ -1027,7 +1083,7 @@ describe.concurrent("scripts/crabbox-wrapper", () => { expect(remoteCommand.indexOf("-S|--split-string|-S*|--split-string=*)")).toBeLessThan( remoteCommand.indexOf("-[!-]*i*)"), ); - expectGroupedShellCommand(remoteCommand, "/usr/bin/env -S 'pnpm --version'"); + expectGroupedShellCommand(remoteCommand, "openclaw_crabbox_env -S 'pnpm --version'"); }); it("bootstraps Corepack for AWS macOS node changed-gate commands", () => { @@ -2324,6 +2380,48 @@ describe.concurrent("scripts/crabbox-wrapper", () => { ); }); + it("preserves sparse changed-gate Git bootstrap for direct absolute env -i commands", () => { + const result = runWrapper( + "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", + ["run", "--provider", "aws", "--", "/usr/bin/env", "-i", "pnpm", "check:changed"], + { + gitResponses: { + [GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" }, + [GIT_STATUS_PORCELAIN_KEY]: { stdout: "" }, + [GIT_MERGE_BASE_MAIN_HEAD_KEY]: { stdout: "abc123\n" }, + }, + }, + ); + + const output = parseFakeCrabboxOutput(result); + const remoteCommand = normalizeShellLineEndings(output.args.at(-1) ?? ""); + expect(result.status).toBe(0); + expect(output.args).toContain("--shell"); + expect(remoteCommand).toContain("git init -q"); + expect(remoteCommand).toMatch( + /&& \/usr\/bin\/env -i OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 CI=1 pnpm check:changed$/u, + ); + }); + + it("does not mark custom env executables outside the sanitized env", () => { + const result = runWrapper( + "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n", + ["run", "--provider", "aws", "--", "./tools/env", "-i", "pnpm", "check:changed"], + { + gitResponses: { + [GIT_CONFIG_SPARSE_KEY]: { stdout: "true\n" }, + [GIT_STATUS_PORCELAIN_KEY]: { stdout: "" }, + [GIT_MERGE_BASE_MAIN_HEAD_KEY]: { stdout: "abc123\n" }, + }, + }, + ); + + const output = parseFakeCrabboxOutput(result); + expect(result.status).toBe(0); + expect(output.args.join("\0")).not.toContain("OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1"); + expect(output.args.join("\0")).not.toContain("git init -q"); + }); + it("does not mark assignment-prefixed env -i changed gates outside the sanitized env", () => { const result = runWrapper( "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",