diff --git a/CHANGELOG.md b/CHANGELOG.md index 841b4239..6b1484ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes +- CLI: pass Codex image attachments to `codex exec` so local image summaries no longer fail before starting (#242, #243, thanks @alfozan). - Chrome extension: abort stale side-panel summary streams on tab changes so delayed output from a closed or replaced tab cannot render under the new page title. - Core: extract video IDs from YouTube `/live/` URLs so live and premiere links no longer abort summarization (#232, thanks @devYRPauli). - Chrome extension: keep YouTube slide cards on the shared slide-summary path so local browser thumbnails receive the same summary text shape as CLI `--slides`. diff --git a/src/llm/cli.ts b/src/llm/cli.ts index deee9647..d16a388d 100644 --- a/src/llm/cli.ts +++ b/src/llm/cli.ts @@ -304,8 +304,7 @@ export async function runCliModel({ if (isolatedCodexHome) { await copyCodexAuthFiles(effectiveEnv.CODEX_HOME, isolatedCodexHome); } - args.push(...codexExtraArgs); - args.push("exec"); + args.push("exec", ...codexExtraArgs); if (shouldIsolateCodex) { args.push("--ephemeral", "--ignore-user-config", "--ignore-rules"); if (isolatedCwd) args.push("-C", isolatedCwd); diff --git a/tests/llm.cli.regressions.test.ts b/tests/llm.cli.regressions.test.ts index dec519dc..b2568955 100644 --- a/tests/llm.cli.regressions.test.ts +++ b/tests/llm.cli.regressions.test.ts @@ -99,6 +99,103 @@ describe("runCliModel regressions", () => { expect(result.text).toBe("Hello world"); }); + it("passes Codex image args after exec so -i does not consume the subcommand", async () => { + let seenArgs: string[] = []; + let stdinText = ""; + const prompt = "summarize this image"; + + const result = await runCliModel({ + provider: "codex", + prompt, + model: "gpt-5.2", + allowTools: true, + timeoutMs: 1000, + env: {}, + config: null, + extraArgs: ["-i", "/tmp/image-1.jpg", "/tmp/image-2.jpg"], + execFileImpl: (_cmd, args, _opts, cb) => { + seenArgs = [...args]; + const outputIndex = args.indexOf("--output-last-message"); + const outputPath = outputIndex >= 0 ? args[outputIndex + 1] : null; + if (!outputPath) throw new Error("missing output path"); + writeFileSync(outputPath, "ok", "utf8"); + cb(null, "", ""); + return { + stdin: { + write(value: string) { + stdinText += value; + }, + end() {}, + }, + } as unknown as ChildProcess; + }, + }); + + const execIndex = seenArgs.indexOf("exec"); + const imageFlagIndex = seenArgs.indexOf("-i"); + const firstImageIndex = seenArgs.indexOf("/tmp/image-1.jpg"); + const secondImageIndex = seenArgs.indexOf("/tmp/image-2.jpg"); + const outputIndex = seenArgs.indexOf("--output-last-message"); + const modelIndex = seenArgs.indexOf("-m"); + const verbosityIndex = seenArgs.indexOf("-c"); + + expect(result.text).toBe("ok"); + expect(execIndex).toBe(0); + expect(imageFlagIndex).toBeGreaterThan(execIndex); + expect(firstImageIndex).toBe(imageFlagIndex + 1); + expect(secondImageIndex).toBe(firstImageIndex + 1); + expect(outputIndex).toBeGreaterThan(secondImageIndex); + expect(modelIndex).toBeGreaterThan(execIndex); + expect(verbosityIndex).toBeGreaterThan(execIndex); + expect(seenArgs).not.toContain(prompt); + expect(stdinText).toBe(prompt); + }); + + it("keeps generated Codex flags under exec for non-image summaries", async () => { + let seenArgs: string[] = []; + + const result = await runCliModel({ + provider: "codex", + prompt: "summarize text", + model: "gpt-fast", + allowTools: false, + timeoutMs: 1000, + env: {}, + config: { codex: { extraArgs: ["--sandbox", "read-only"] } }, + execFileImpl: (_cmd, args, _opts, cb) => { + seenArgs = [...args]; + const outputIndex = args.indexOf("--output-last-message"); + const outputPath = outputIndex >= 0 ? args[outputIndex + 1] : null; + if (!outputPath) throw new Error("missing output path"); + writeFileSync(outputPath, "ok", "utf8"); + cb(null, "", ""); + return { stdin: { write() {}, end() {} } } as unknown as ChildProcess; + }, + }); + + const execIndex = seenArgs.indexOf("exec"); + + expect(result.text).toBe("ok"); + expect(execIndex).toBe(0); + expect(seenArgs).not.toContain("-i"); + for (const flag of [ + "--sandbox", + "--ephemeral", + "--ignore-user-config", + "--ignore-rules", + "-C", + "--output-last-message", + "--skip-git-repo-check", + "--json", + "-m", + "-c", + ]) { + expect(seenArgs.indexOf(flag)).toBeGreaterThan(execIndex); + } + expect(seenArgs).toContain("gpt-5.5"); + expect(seenArgs).toContain('service_tier="fast"'); + }); + it("codex does not leak lifecycle JSONL when no assistant text was produced", async () => { await expect( runCliModel({