From 43f30b8a83c329a63b12ae976d3eea2c37657445 Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 11 Aug 2026 09:01:33 +0000 Subject: [PATCH] fix(coding-agent): emit clap-compatible CI=true and honor PI_BASH_NO_CI The per-command non-interactive env hardcoded an unguarded CI=1, which clap parses as a strict boolean and rejects (breaking e.g. tauri android build). It also overrode the session env, so the documented PI_BASH_NO_CI opt-out never reached bash-tool commands. buildNonInteractiveEnv now injects CI=true and strips CI when PI_BASH_NO_CI/CLAUDE_BASH_NO_CI is set, matching buildSpawnEnv in procmgr.ts. Fixes #8229 --- docs/bash-tool-runtime.md | 2 +- packages/coding-agent/CHANGELOG.md | 4 ++++ .../src/exec/non-interactive-env.ts | 17 ++++++++++++++--- .../test/non-interactive-env.test.ts | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/bash-tool-runtime.md b/docs/bash-tool-runtime.md index f10f0bc3d..8def59c77 100644 --- a/docs/bash-tool-runtime.md +++ b/docs/bash-tool-runtime.md @@ -140,7 +140,7 @@ The per-command child environment is then built by `buildNonInteractiveEnv()` (` - pagers disabled (`PAGER=cat`, `GIT_PAGER=cat`, … and `LESS=FRX`), - editor prompts disabled (`GIT_EDITOR=true`, `EDITOR=true`, `VISUAL=true`), -- terminal/credential prompts reduced (`TERM=dumb`, `GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `NO_COLOR=1`, `CI=1`), +- terminal/credential prompts reduced (`TERM=dumb`, `GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `NO_COLOR=1`, `CI=true` unless `PI_BASH_NO_CI`/`CLAUDE_BASH_NO_CI` is set), - package-manager/tooling automation flags for non-interactive behavior (npm/pnpm/yarn/pip/cargo/terraform/gh, …), - on Windows, UTF-8 locale/codepage defaults are added when absent. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a4d66e8d2..c9bc4c66d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed bash-tool commands receiving an unguarded `CI=1`, which broke clap-based CLIs (e.g. `tauri android build`) that parse `CI` as a strict boolean, and ignored the documented `PI_BASH_NO_CI` opt-out. The per-command env now injects clap-compatible `CI=true` and honors `PI_BASH_NO_CI`/`CLAUDE_BASH_NO_CI` ([#8229](https://github.com/can1357/oh-my-pi/issues/8229)). + ## [17.2.12] - 2026-08-08 ### Fixed diff --git a/packages/coding-agent/src/exec/non-interactive-env.ts b/packages/coding-agent/src/exec/non-interactive-env.ts index 42f6bab2a..3b1846639 100644 --- a/packages/coding-agent/src/exec/non-interactive-env.ts +++ b/packages/coding-agent/src/exec/non-interactive-env.ts @@ -23,7 +23,7 @@ export const NON_INTERACTIVE_ENV: Readonly> = { EDITOR: "true", GIT_TERMINAL_PROMPT: "0", SSH_ASKPASS: "/usr/bin/false", - CI: "1", + CI: "true", AGENT: "1", // Package manager defaults for unattended execution. npm_config_yes: "true", @@ -96,17 +96,28 @@ function hasEnvGroupValue( return false; } +/** Copy of the base env with `CI` removed, for the `PI_BASH_NO_CI` opt-out. */ +function withoutCI(env: Readonly>): Record { + const { CI: _ci, ...rest } = env; + return rest; +} + /** Builds the per-command environment for non-interactive child processes. */ export function buildNonInteractiveEnv( overrides?: Record, baseEnv: Record = Bun.env, platform: NodeJS.Platform = process.platform, ): Record { + // `PI_BASH_NO_CI` (and its legacy alias) opts out of the automatic `CI=true` + // injection. Mirrors the session-env gate in `procmgr.ts` so the opt-out + // reaches the per-command env, which otherwise overrides the session value. + const base = + baseEnv.PI_BASH_NO_CI || baseEnv.CLAUDE_BASH_NO_CI ? withoutCI(NON_INTERACTIVE_ENV) : NON_INTERACTIVE_ENV; if (platform !== "win32") { - return overrides ? { ...NON_INTERACTIVE_ENV, ...overrides } : NON_INTERACTIVE_ENV; + return overrides ? { ...base, ...overrides } : base; } - const env: Record = { ...NON_INTERACTIVE_ENV }; + const env: Record = { ...base }; for (const group of WINDOWS_UTF8_ENV_DEFAULT_GROUPS) { if (hasEnvGroupValue(baseEnv, group, platform) || hasEnvGroupValue(overrides, group, platform)) { continue; diff --git a/packages/coding-agent/test/non-interactive-env.test.ts b/packages/coding-agent/test/non-interactive-env.test.ts index 068ac61f9..8057475a7 100644 --- a/packages/coding-agent/test/non-interactive-env.test.ts +++ b/packages/coding-agent/test/non-interactive-env.test.ts @@ -59,6 +59,21 @@ describe("buildNonInteractiveEnv", () => { expect(env.GPG_TTY).toBe("/dev/pts/7"); }); + + it("injects clap-compatible CI=true by default", () => { + expect(buildNonInteractiveEnv(undefined, {}, "linux").CI).toBe("true"); + expect(buildNonInteractiveEnv(undefined, {}, "win32").CI).toBe("true"); + }); + + it("drops CI when PI_BASH_NO_CI or its legacy alias is set", () => { + expect(buildNonInteractiveEnv(undefined, { PI_BASH_NO_CI: "1" }, "linux")).not.toHaveProperty("CI"); + expect(buildNonInteractiveEnv(undefined, { CLAUDE_BASH_NO_CI: "1" }, "linux")).not.toHaveProperty("CI"); + expect(buildNonInteractiveEnv(undefined, { PI_BASH_NO_CI: "1" }, "win32")).not.toHaveProperty("CI"); + }); + + it("lets a per-command CI override win over the opt-out", () => { + expect(buildNonInteractiveEnv({ CI: "0" }, { PI_BASH_NO_CI: "1" }, "linux").CI).toBe("0"); + }); }); it("filters expanded dotenv values while preserving matching launcher values", async () => {