diff --git a/packages/coding-agent/src/cli.ts b/packages/coding-agent/src/cli.ts index 184766db1..8492b05c6 100755 --- a/packages/coding-agent/src/cli.ts +++ b/packages/coding-agent/src/cli.ts @@ -1,5 +1,12 @@ #!/usr/bin/env bun -import { APP_NAME, getActiveProfile, MIN_BUN_VERSION, setProfile, VERSION } from "@oh-my-pi/pi-utils/dirs"; +import { + APP_NAME, + getActiveProfile, + MIN_BUN_VERSION, + normalizeProfileName, + setProfile, + VERSION, +} from "@oh-my-pi/pi-utils/dirs"; // Strip macOS malloc-stack-logging env vars before any subprocess is spawned. // Keep this local instead of importing `@oh-my-pi/pi-utils/procmgr`: that module @@ -60,6 +67,13 @@ export async function runCli(argv: string[]): Promise { resolvedArgv = extracted.argv; if (extracted.profile !== undefined) { setProfile(extracted.profile); + } else { + // No explicit --profile: re-validate any OMP_PROFILE/PI_PROFILE inherited + // from the environment. Module-load resolution deliberately swallows an + // invalid value to avoid an uncaught throw before this try/catch is in + // scope (see `readProfileFromEnvSafe` in dirs.ts). Surfacing it here turns + // `OMP_PROFILE=.. omp --version` into a clean error instead of a stack trace. + normalizeProfileName(process.env.OMP_PROFILE || process.env.PI_PROFILE); } if (extracted.aliasName !== undefined) { const profile = extracted.profile ?? getActiveProfile(); diff --git a/packages/coding-agent/test/profile-cli.test.ts b/packages/coding-agent/test/profile-cli.test.ts index 2edd62842..720e7e733 100644 --- a/packages/coding-agent/test/profile-cli.test.ts +++ b/packages/coding-agent/test/profile-cli.test.ts @@ -168,4 +168,53 @@ describe("global --profile flag", () => { await fs.rm(root, { recursive: true, force: true }); } }); + + it("surfaces an invalid OMP_PROFILE env as a clean error, not an import crash", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-profile-cli-env-bad-")); + try { + const home = path.join(root, "home"); + await fs.mkdir(home, { recursive: true }); + + const probePath = path.join(root, "probe.ts"); + await Bun.write( + probePath, + [ + `import { runCli } from ${JSON.stringify(url.pathToFileURL(cliEntry).href)};`, + 'await runCli(["--version"]);', + // Reached only if the module import did NOT throw — i.e. the invalid + // env was deferred to runCli's error handler instead of crashing the + // process during the static import of dirs.ts. + 'process.stdout.write("HANDLED");', + ].join("\n"), + ); + + const childEnv: Record = { + ...process.env, + HOME: home, + PI_CONFIG_DIR: ".omp-profile-cli-env-bad", + OMP_PROFILE: "..", + NO_COLOR: "1", + }; + delete childEnv.PI_PROFILE; + delete childEnv.PI_CODING_AGENT_DIR; + + const proc = Bun.spawn([process.execPath, probePath], { + cwd: repoRoot, + stdout: "pipe", + stderr: "pipe", + env: childEnv, + }); + const [stdout, stderr, exitCode] = await Promise.all([ + readStream(proc.stdout as ReadableStream), + readStream(proc.stderr as ReadableStream), + proc.exited, + ]); + + expect(stdout, stderr).toContain("HANDLED"); + expect(stderr).toContain("Invalid OMP profile"); + expect(exitCode).toBe(1); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); });