diff --git a/packages/integration-tests/src/agent-codex-launch-env.integration.test.ts b/packages/integration-tests/src/agent-codex-launch-env.integration.test.ts new file mode 100644 index 000000000..2d76089b0 --- /dev/null +++ b/packages/integration-tests/src/agent-codex-launch-env.integration.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import type { AgentLaunchConfig } from "@composio/ao-core"; +import codexPlugin from "@composio/ao-plugin-agent-codex"; + +function makeLaunchConfig(overrides: Partial = {}): AgentLaunchConfig { + return { + sessionId: "sess-int-1", + projectConfig: { + name: "my-project", + repo: "owner/repo", + path: "/workspace/repo", + defaultBranch: "main", + sessionPrefix: "my", + }, + ...overrides, + }; +} + +describe("agent-codex launch/env wiring (integration)", () => { + const agent = codexPlugin.create(); + + it("includes check_for_update_on_startup=false in launch command", () => { + const cmd = agent.getLaunchCommand(makeLaunchConfig()); + expect(cmd).toContain("-c check_for_update_on_startup=false"); + }); + + it("preserves update-check override alongside other flags", () => { + const cmd = agent.getLaunchCommand( + makeLaunchConfig({ + permissions: "default", + model: "o3-mini", + prompt: "Do the thing", + }), + ); + expect(cmd).toContain("-c check_for_update_on_startup=false"); + expect(cmd).not.toContain("--ask-for-approval"); + expect(cmd).toContain("--model 'o3-mini'"); + expect(cmd).toContain("-c model_reasoning_effort=high"); + }); + + it("sets CODEX_DISABLE_UPDATE_CHECK=1 in environment", () => { + const env = agent.getEnvironment(makeLaunchConfig()); + expect(env["CODEX_DISABLE_UPDATE_CHECK"]).toBe("1"); + }); +}); diff --git a/packages/plugins/agent-codex/src/index.test.ts b/packages/plugins/agent-codex/src/index.test.ts index c6ecabef8..f2469b1cb 100644 --- a/packages/plugins/agent-codex/src/index.test.ts +++ b/packages/plugins/agent-codex/src/index.test.ts @@ -210,7 +210,7 @@ describe("getLaunchCommand", () => { const agent = create(); it("generates base command", () => { - expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'codex'"); + expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'codex' -c check_for_update_on_startup=false"); }); it("includes bypass flag when permissions=permissionless", () => { @@ -258,7 +258,7 @@ describe("getLaunchCommand", () => { const cmd = agent.getLaunchCommand( makeLaunchConfig({ permissions: "permissionless", model: "o3", prompt: "Go" }), ); - expect(cmd).toBe("'codex' --dangerously-bypass-approvals-and-sandbox --model 'o3' -c model_reasoning_effort=high -- 'Go'"); + expect(cmd).toBe("'codex' -c check_for_update_on_startup=false --dangerously-bypass-approvals-and-sandbox --model 'o3' -c model_reasoning_effort=high -- 'Go'"); }); it("escapes single quotes in prompt (POSIX shell escaping)", () => { @@ -297,10 +297,15 @@ describe("getLaunchCommand", () => { expect(cmd).not.toContain("--dangerously-bypass-approvals-and-sandbox"); expect(cmd).not.toContain("--ask-for-approval"); expect(cmd).not.toContain("--model"); - expect(cmd).not.toContain("-c"); + expect(cmd).toContain("-c check_for_update_on_startup=false"); expect(cmd).not.toContain("model_reasoning_effort"); }); + it("always includes -c check_for_update_on_startup=false", () => { + const cmd = agent.getLaunchCommand(makeLaunchConfig({ model: "gpt-4o", prompt: "Fix it" })); + expect(cmd).toContain("-c check_for_update_on_startup=false"); + }); + // -- Reasoning effort tests -- describe("reasoning effort", () => { it("adds model_reasoning_effort=high for o3 model", () => { @@ -377,6 +382,11 @@ describe("getEnvironment", () => { expect(env["PATH"]?.startsWith("/mock/home/.ao/bin:")).toBe(true); }); + it("sets CODEX_DISABLE_UPDATE_CHECK=1 to suppress interactive update prompts", () => { + const env = agent.getEnvironment(makeLaunchConfig()); + expect(env["CODEX_DISABLE_UPDATE_CHECK"]).toBe("1"); + }); + it("falls back to /usr/bin:/bin when process.env.PATH is undefined", () => { const originalPath = process.env["PATH"]; delete process.env["PATH"]; @@ -965,6 +975,7 @@ describe("getRestoreCommand", () => { expect(cmd).not.toBeNull(); expect(cmd).toContain("'codex' resume"); + expect(cmd).toContain("-c check_for_update_on_startup=false"); expect(cmd).toContain("thread-abc-123"); }); @@ -1236,13 +1247,13 @@ describe("postLaunchSetup", () => { mockReadFile.mockRejectedValue(new Error("ENOENT")); // Before postLaunchSetup, binary is "codex" - expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'codex'"); + expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'codex' -c check_for_update_on_startup=false"); // After postLaunchSetup resolves the binary await agent.postLaunchSetup!(makeSession({ workspacePath: "/workspace/test" })); // Now getLaunchCommand should use the resolved binary - expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'/opt/bin/codex'"); + expect(agent.getLaunchCommand(makeLaunchConfig())).toBe("'/opt/bin/codex' -c check_for_update_on_startup=false"); }); }); diff --git a/packages/plugins/agent-codex/src/index.ts b/packages/plugins/agent-codex/src/index.ts index 1f48c383e..7e83aa579 100644 --- a/packages/plugins/agent-codex/src/index.ts +++ b/packages/plugins/agent-codex/src/index.ts @@ -548,6 +548,11 @@ function appendModelFlags(parts: string[], model: string | undefined): void { } } +/** Disable Codex startup update checks/prompts in non-interactive sessions */ +function appendNoUpdateCheckFlag(parts: string[]): void { + parts.push("-c", "check_for_update_on_startup=false"); +} + /** TTL for session file path cache (ms). Prevents redundant filesystem scans * when getActivityState and getSessionInfo are called in the same refresh cycle. */ const SESSION_FILE_CACHE_TTL_MS = 30_000; @@ -580,6 +585,7 @@ function createCodexAgent(): Agent { getLaunchCommand(config: AgentLaunchConfig): string { const binary = resolvedBinary ?? "codex"; const parts: string[] = [shellEscape(binary)]; + appendNoUpdateCheckFlag(parts); appendApprovalFlags(parts, config.permissions); appendModelFlags(parts, config.model); @@ -613,6 +619,8 @@ function createCodexAgent(): Agent { // The wrappers strip this directory from PATH before calling the real // binary, so there's no infinite recursion. env["PATH"] = `${AO_BIN_DIR}:${process.env["PATH"] ?? "/usr/bin:/bin"}`; + // Disable Codex's version check/update prompt for non-interactive AO sessions. + env["CODEX_DISABLE_UPDATE_CHECK"] = "1"; return env; }, @@ -769,6 +777,7 @@ function createCodexAgent(): Agent { // Flags are placed before the positional threadId for CLI parser compatibility. const binary = resolvedBinary ?? "codex"; const parts: string[] = [shellEscape(binary), "resume"]; + appendNoUpdateCheckFlag(parts); appendApprovalFlags(parts, project.agentConfig?.permissions); const effectiveModel = (project.agentConfig?.model ?? data.model) as string | undefined;