From 487e33b8b6962a7dd922c6855ed5bee309f59982 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:15:08 -0500 Subject: [PATCH] fix(codex-local): resolve GPT-5.6 model metadata at source (#9780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents for work > - The `codex_local` adapter runs OpenAI's Codex CLI through direct CLI and ACP execution lanes > - The adapter defaulted to the bare `gpt-5.6` alias while the bundled ACP Codex version lacked GPT-5.6-family metadata > - Default and legacy-configured runs therefore emitted fallback-metadata warnings and could use generic context limits > - This pull request upgrades the bundled Codex ACP dependency, selects the concrete `gpt-5.6-sol` model, and normalizes the legacy alias in both execution lanes > - The benefit is correct model metadata without hiding genuine stderr or transcript warnings ## Linked Issues or Issue Description Related public PRs: Refs #9342, Refs #9352, and Refs #9382. This PR is narrower: it upgrades bundled Codex metadata and normalizes the legacy bare alias in both execution lanes. **Bug report** ### What happened Default `codex_local` runs, and agents still configured with the bare `gpt-5.6` model, print a model-metadata fallback warning and use generic context-window limits. Root cause: the ACP lane bundled a Codex release predating GPT-5.6-family metadata, while Paperclip's default and advertised model used the bare `gpt-5.6` alias for which Codex publishes no metadata. ### Expected behavior A default Codex run resolves to a concrete model slug with published metadata and does not emit a fallback-metadata warning. ### Deployment mode Self-hosted/local `codex_local` adapter. ## What Changed - Upgraded `@agentclientprotocol/codex-acp` from `^1.1.0` to `^1.1.4` - Changed `DEFAULT_CODEX_LOCAL_MODEL` from `gpt-5.6` to `gpt-5.6-sol` - Removed the bare alias from advertised models and listed concrete GPT-5.6 Fast-mode variants - Added `normalizeCodexModel()` and applied it in both CLI and ACP execution lanes - Updated adapter docs, Storybook fixtures, and regression tests - Preserved warning visibility; no stderr, transcript, or log filtering changed ## Verification - `pnpm --filter @paperclipai/adapter-codex-local typecheck` - `pnpm check:token-gates` - `cd packages/adapters/codex-local && pnpm exec vitest run` — 205 tests passed - `cd server && pnpm exec vitest run src/__tests__/adapter-models.test.ts` — 17 tests passed - Confirmed the PR diff excludes `pnpm-lock.yaml` and `.github/workflows/**` as required by repository policy - Confirmed `.github/workflows/pr.yml` regenerates and uploads the PR lockfile artifact before downstream `pnpm install --frozen-lockfile` steps ## Risks Low risk. The behavior change is scoped to `codex_local` model selection. Existing concrete model IDs pass through unchanged; only the legacy bare `gpt-5.6` alias is rewritten. Dependency resolution may select a newer compatible `codex-acp` release within the declared range, so CI remains the final compatibility gate. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Original implementation: Anthropic Claude Opus 4.8 (`claude-opus-4-8`, 1M context, tool use and code execution) - Conflict resolution and PR preparation: OpenAI GPT-5.5 (`gpt-5.5`, Codex CLI coding agent, high-reasoning tool use and code execution; host-managed context window) ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details — branch name is fixed by the assigned execution workspace and cannot be renamed in-place - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- packages/adapters/codex-local/package.json | 2 +- .../adapters/codex-local/src/index.test.ts | 26 ++++++++++++--- packages/adapters/codex-local/src/index.ts | 32 ++++++++++++++++--- .../codex-local/src/server/acp.test.ts | 6 ++++ .../adapters/codex-local/src/server/acp.ts | 7 ++++ .../codex-local/src/server/codex-args.test.ts | 15 ++++++++- .../codex-local/src/server/codex-args.ts | 3 +- server/src/__tests__/adapter-models.test.ts | 3 +- .../stories/issue-management.stories.tsx | 2 +- 9 files changed, 82 insertions(+), 14 deletions(-) diff --git a/packages/adapters/codex-local/package.json b/packages/adapters/codex-local/package.json index 238d85a5ada..32e393eb772 100644 --- a/packages/adapters/codex-local/package.json +++ b/packages/adapters/codex-local/package.json @@ -52,7 +52,7 @@ "probe:quota": "pnpm exec tsx src/cli/quota-probe.ts --json" }, "dependencies": { - "@agentclientprotocol/codex-acp": "^1.1.0", + "@agentclientprotocol/codex-acp": "^1.1.4", "@paperclipai/adapter-utils": "workspace:*", "picocolors": "^1.1.1" }, diff --git a/packages/adapters/codex-local/src/index.test.ts b/packages/adapters/codex-local/src/index.test.ts index 9cb95891221..659d34c61f3 100644 --- a/packages/adapters/codex-local/src/index.test.ts +++ b/packages/adapters/codex-local/src/index.test.ts @@ -1,19 +1,37 @@ import { describe, expect, it } from "vitest"; -import { DEFAULT_CODEX_LOCAL_MODEL, isCodexLocalFastModeSupported, models } from "./index.js"; +import { + DEFAULT_CODEX_LOCAL_MODEL, + isCodexLocalFastModeSupported, + models, + normalizeCodexModel, +} from "./index.js"; describe("codex local adapter metadata", () => { it("advertises current GPT-5.6 Codex-capable OpenAI models by default", () => { const modelIds = models.map((model) => model.id); - expect(DEFAULT_CODEX_LOCAL_MODEL).toBe("gpt-5.6"); - expect(modelIds.slice(0, 4)).toEqual([ - "gpt-5.6", + // Default to the concrete gpt-5.6-sol slug — Codex ships no metadata for the bare gpt-5.6 + // alias, so it must not be advertised or used as the default (it triggers a fallback warning). + expect(DEFAULT_CODEX_LOCAL_MODEL).toBe("gpt-5.6-sol"); + expect(modelIds.slice(0, 3)).toEqual([ "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", ]); + expect(modelIds).not.toContain("gpt-5.6"); expect(isCodexLocalFastModeSupported(DEFAULT_CODEX_LOCAL_MODEL)).toBe(true); expect(modelIds).not.toContain("gpt-5.3-codex"); expect(modelIds).not.toContain("gpt-5.3-codex-spark"); }); + + it("normalizes the legacy bare gpt-5.6 alias to the concrete gpt-5.6-sol slug", () => { + expect(normalizeCodexModel("gpt-5.6")).toBe("gpt-5.6-sol"); + expect(normalizeCodexModel(" gpt-5.6 ")).toBe("gpt-5.6-sol"); + // Concrete slugs and unknown/manual model IDs pass through untouched. + expect(normalizeCodexModel("gpt-5.6-sol")).toBe("gpt-5.6-sol"); + expect(normalizeCodexModel("gpt-5.5")).toBe("gpt-5.5"); + expect(normalizeCodexModel("future-model")).toBe("future-model"); + expect(normalizeCodexModel("")).toBe(""); + expect(normalizeCodexModel(null)).toBe(""); + }); }); diff --git a/packages/adapters/codex-local/src/index.ts b/packages/adapters/codex-local/src/index.ts index bddcc3dc7b4..c29388a457b 100644 --- a/packages/adapters/codex-local/src/index.ts +++ b/packages/adapters/codex-local/src/index.ts @@ -5,14 +5,36 @@ export const label = "Codex"; export const SANDBOX_INSTALL_COMMAND = "npm install -g @openai/codex"; -export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.6"; +// Use the concrete `gpt-5.6-sol` slug (Codex's own default for the 5.6 family) rather than the +// bare `gpt-5.6` alias: OpenAI ships no model metadata for the bare slug, so passing it makes the +// Codex CLI warn ("Model metadata for `gpt-5.6` not found") and fall back to generic context limits. +export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.6-sol"; export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true; -export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = ["gpt-5.6", "gpt-5.5", "gpt-5.4"] as const; +export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = [ + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-5.6-luna", + "gpt-5.5", + "gpt-5.4", +] as const; function normalizeModelId(model: string | null | undefined): string { return typeof model === "string" ? model.trim() : ""; } +// Legacy model aliases with no OpenAI-published metadata are rewritten to the concrete slug the +// Codex CLI actually knows. Without this, agents still configured with the bare `gpt-5.6` alias +// (the old default) keep triggering "Model metadata for `gpt-5.6` not found" warnings and Codex +// falls back to generic context-window limits, even on a Codex build that ships the 5.6 family. +const CODEX_LOCAL_MODEL_ALIASES: Readonly> = { + "gpt-5.6": "gpt-5.6-sol", +}; + +export function normalizeCodexModel(model: string | null | undefined): string { + const normalizedModel = normalizeModelId(model); + return CODEX_LOCAL_MODEL_ALIASES[normalizedModel] ?? normalizedModel; +} + export function isCodexLocalKnownModel(model: string | null | undefined): boolean { const normalizedModel = normalizeModelId(model); if (!normalizedModel) return false; @@ -37,8 +59,8 @@ export function isCodexLocalFastModeSupported(model: string | null | undefined): } export const models = [ + // DEFAULT_CODEX_LOCAL_MODEL is gpt-5.6-sol, so it doubles as the first (default) 5.6 entry. { id: DEFAULT_CODEX_LOCAL_MODEL, label: DEFAULT_CODEX_LOCAL_MODEL }, - { id: "gpt-5.6-sol", label: "gpt-5.6-sol" }, { id: "gpt-5.6-terra", label: "gpt-5.6-terra" }, { id: "gpt-5.6-luna", label: "gpt-5.6-luna" }, { id: "gpt-5.4", label: "gpt-5.4" }, @@ -74,7 +96,7 @@ Core fields: - modelReasoningEffort (string, optional): reasoning effort override (minimal|low|medium|high|xhigh) passed via -c model_reasoning_effort=... - promptTemplate (string, optional): run prompt template - search (boolean, optional): run codex with --search -- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-5.6, GPT-5.5, GPT-5.4 and passed through for manual model IDs +- fastMode (boolean, optional): enable Codex Fast mode; supported on GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and passed through for manual model IDs - dangerouslyBypassApprovalsAndSandbox (boolean, optional): run with bypass flag - command (string, optional): defaults to "codex" - extraArgs (string[], optional): additional CLI args @@ -105,7 +127,7 @@ Notes: - Paperclip injects desired local skills into the effective CODEX_HOME/skills/ directory at execution time so Codex can discover "$paperclip" and related skills without polluting the project working directory. For new and updated agents, Paperclip assigns an isolated managed home at ~/.paperclip/instances//companies//agents//codex-home/skills/; when CODEX_HOME is explicitly overridden in adapter config, that override is used instead. - New and updated codex_local agents persist an empty OPENAI_API_KEY override by default so a host-level OPENAI_API_KEY cannot leak into Codex runs through process inheritance. Explicit CODEX_HOME overrides must not point at the shared company codex-home, $CODEX_HOME, or ~/.codex. - Some model/tool combinations reject certain effort levels (for example minimal with web search enabled). -- Fast mode is supported on GPT-5.6, GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`. +- Fast mode is supported on GPT-5.6 (sol/terra/luna), GPT-5.5, GPT-5.4 and manual model IDs. When enabled for those models, Paperclip applies \`service_tier="fast"\` and \`features.fast_mode=true\`. - When Paperclip realizes a workspace/runtime for a run, it injects PAPERCLIP_WORKSPACE_* and PAPERCLIP_RUNTIME_* env vars for agent-side tooling. - Codex ACP is the preferred auto lane when Node >=22.13.0 and the Codex ACP server are available. It reuses shared ACP prompt/runtime guidance, selected skill materialization into CODEX_HOME/skills, model/reasoning/fast-mode session config, and existing quota-window reporting. Auto selection falls back to CLI when ACP prerequisites are unavailable; explicit engine="acp" fails loudly. `; diff --git a/packages/adapters/codex-local/src/server/acp.test.ts b/packages/adapters/codex-local/src/server/acp.test.ts index b789542efa3..c12743e4445 100644 --- a/packages/adapters/codex-local/src/server/acp.test.ts +++ b/packages/adapters/codex-local/src/server/acp.test.ts @@ -469,6 +469,12 @@ describe("codex_local ACP lane", () => { }); }); + it("normalizes the legacy bare gpt-5.6 alias to gpt-5.6-sol", () => { + expect(buildCodexAcpConfig({ engine: "acp", model: "gpt-5.6" })).toMatchObject({ + model: "gpt-5.6-sol", + }); + }); + it("checks the Node version required by the ACPX runtime", () => { setNodeVersion("v22.12.0"); expect(nodeVersionMeetsCodexAcpMinimum()).toBe(false); diff --git a/packages/adapters/codex-local/src/server/acp.ts b/packages/adapters/codex-local/src/server/acp.ts index 351ce5d1c1e..c604d7ea6ba 100644 --- a/packages/adapters/codex-local/src/server/acp.ts +++ b/packages/adapters/codex-local/src/server/acp.ts @@ -35,6 +35,7 @@ import { asString, parseObject, } from "@paperclipai/adapter-utils/server-utils"; +import { normalizeCodexModel } from "../index.js"; import { classifyCodexAuthRefreshFailure } from "./parse.js"; import { copyBackCodexAuth } from "./codex-auth-copyback.js"; import { buildCodexAuthInboundProvision } from "./codex-auth-merge-scripts.js"; @@ -145,6 +146,11 @@ export function buildCodexAcpConfig(config: Record): Record): Record { + it("rewrites the legacy bare gpt-5.6 alias to gpt-5.6-sol and applies fast mode", () => { + const result = buildCodexExecArgs({ + model: "gpt-5.6", + fastMode: true, + }); + + expect(result.model).toBe("gpt-5.6-sol"); + expect(result.args).toContain("gpt-5.6-sol"); + expect(result.args).not.toContain("gpt-5.6"); + expect(result.fastModeApplied).toBe(true); + expect(result.fastModeIgnoredReason).toBeNull(); + }); + it("enables Codex fast mode overrides for GPT-5.4", () => { const result = buildCodexExecArgs({ model: "gpt-5.4", @@ -98,7 +111,7 @@ describe("buildCodexExecArgs", () => { expect(result.fastModeRequested).toBe(true); expect(result.fastModeApplied).toBe(false); expect(result.fastModeIgnoredReason).toContain( - "currently only supported on gpt-5.6, gpt-5.5, gpt-5.4 or manually configured model IDs", + "currently only supported on gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4 or manually configured model IDs", ); expect(result.args).toEqual([ "exec", diff --git a/packages/adapters/codex-local/src/server/codex-args.ts b/packages/adapters/codex-local/src/server/codex-args.ts index 615f4996df5..475345514f7 100644 --- a/packages/adapters/codex-local/src/server/codex-args.ts +++ b/packages/adapters/codex-local/src/server/codex-args.ts @@ -2,6 +2,7 @@ import { asBoolean, asString, asStringArray } from "@paperclipai/adapter-utils/s import { CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS, isCodexLocalFastModeSupported, + normalizeCodexModel, } from "../index.js"; export type BuildCodexExecArgsResult = { @@ -36,7 +37,7 @@ export function buildCodexExecArgs( } = {}, ): BuildCodexExecArgsResult { const record = asRecord(config); - const model = asString(record.model, "").trim(); + const model = normalizeCodexModel(asString(record.model, "")); const modelReasoningEffort = asString( record.modelReasoningEffort, asString(record.reasoningEffort, ""), diff --git a/server/src/__tests__/adapter-models.test.ts b/server/src/__tests__/adapter-models.test.ts index b3de0e6ecb1..ba9faa9e0ae 100644 --- a/server/src/__tests__/adapter-models.test.ts +++ b/server/src/__tests__/adapter-models.test.ts @@ -48,7 +48,8 @@ describe("adapter model listing", () => { const models = await listAdapterModels("codex_local"); expect(models).toEqual(codexFallbackModels); - expect(models.some((model) => model.id === "gpt-5.6")).toBe(true); + // The bare gpt-5.6 alias is intentionally not advertised (Codex has no metadata for it). + expect(models.some((model) => model.id === "gpt-5.6")).toBe(false); expect(models.some((model) => model.id === "gpt-5.6-sol")).toBe(true); expect(models.some((model) => model.id === "gpt-5.6-terra")).toBe(true); expect(models.some((model) => model.id === "gpt-5.6-luna")).toBe(true); diff --git a/ui/storybook/stories/issue-management.stories.tsx b/ui/storybook/stories/issue-management.stories.tsx index 20ba4347559..a3e28572350 100644 --- a/ui/storybook/stories/issue-management.stories.tsx +++ b/ui/storybook/stories/issue-management.stories.tsx @@ -70,7 +70,7 @@ const modelOverrideIssue: Issue = { title: "Verify task-level model override provenance", assigneeAdapterOverrides: { adapterConfig: { - model: "gpt-5.6", + model: "gpt-5.6-sol", modelReasoningEffort: "high", }, },