From 05313567d9921fcfe5876fc35d0b75c0f7b566ff Mon Sep 17 00:00:00 2001 From: hiirott Date: Tue, 30 Jun 2026 09:08:05 +0900 Subject: [PATCH 1/2] fix(app-server): pass `-c model="..."` to `codex app-server` so options.model takes effect When `runAppServerTurn` / `runAppServerReview` are invoked with a `model` option, that value was only delivered via the `thread/start` and `turn/start` requests. However, the spawn of `codex app-server` itself did not include a `-c model="..."` override, so the codex CLI session booted with the CLI's built-in default model. On a ChatGPT account where the default (`gpt-5.3-codex`) is unsupported, every task launched via `codex-companion.mjs task --background --model ` failed with: ``` Codex error: 'gpt-5.3-codex' model is not supported when using Codex with a ChatGPT account. ``` even though the user passed `--model gpt-5.4`. This change threads the model option through to the spawn site: - `lib/app-server.mjs`: when `options.model` is set, add `-c model=${JSON.stringify(model)}` to the spawned argv. JSON quoting yields a valid TOML string literal that the codex CLI's `-c` parser accepts unambiguously (e.g. `model="gpt-5.4"`). - `lib/codex.mjs` `withAppServer`: accept a third `clientOptions` argument and forward it to `CodexAppServerClient.connect` for both the primary and the direct-retry connection. - `lib/codex.mjs` `runAppServerReview` / `runAppServerTurn`: pass `{ model: options.model }` through to `withAppServer`. `interruptAppServerTurn` and `findLatestTaskThread` still create their own short-lived clients without a model override; they are not exercised by the failing `task --background --model` path and are left for a follow-up. Verified locally on macOS / codex-cli 0.130.0 with: ``` node companion.mjs task --background --fresh --model gpt-5.4 "say hi" ``` Before this patch the job failed during `turn/start`; after, the job completes in ~7s with the assistant reply. --- plugins/codex/scripts/lib/app-server.mjs | 6 +++++- plugins/codex/scripts/lib/codex.mjs | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/codex/scripts/lib/app-server.mjs b/plugins/codex/scripts/lib/app-server.mjs index fec105c5..e6a6e4a7 100644 --- a/plugins/codex/scripts/lib/app-server.mjs +++ b/plugins/codex/scripts/lib/app-server.mjs @@ -186,7 +186,11 @@ class SpawnedCodexAppServerClient extends AppServerClientBase { } async initialize() { - this.proc = spawn("codex", ["app-server"], { + const args = ["app-server"]; + if (this.options.model) { + args.push("-c", `model=${JSON.stringify(String(this.options.model))}`); + } + this.proc = spawn("codex", args, { cwd: this.cwd, env: this.options.env, stdio: ["pipe", "pipe", "pipe"], diff --git a/plugins/codex/scripts/lib/codex.mjs b/plugins/codex/scripts/lib/codex.mjs index bf7e8c8c..8b35d80e 100644 --- a/plugins/codex/scripts/lib/codex.mjs +++ b/plugins/codex/scripts/lib/codex.mjs @@ -604,10 +604,10 @@ async function captureTurn(client, threadId, startRequest, options = {}) { } } -async function withAppServer(cwd, fn) { +async function withAppServer(cwd, fn, clientOptions = {}) { let client = null; try { - client = await CodexAppServerClient.connect(cwd); + client = await CodexAppServerClient.connect(cwd, clientOptions); const result = await fn(client); await client.close(); return result; @@ -626,7 +626,7 @@ async function withAppServer(cwd, fn) { throw error; } - const directClient = await CodexAppServerClient.connect(cwd, { disableBroker: true }); + const directClient = await CodexAppServerClient.connect(cwd, { ...clientOptions, disableBroker: true }); try { return await fn(directClient); } finally { @@ -823,7 +823,7 @@ export async function runAppServerReview(cwd, options = {}) { error: turnState.error, stderr: cleanCodexStderr(client.stderr) }; - }); + }, { model: options.model }); } export async function runAppServerTurn(cwd, options = {}) { @@ -890,7 +890,7 @@ export async function runAppServerTurn(cwd, options = {}) { touchedFiles: collectTouchedFiles(turnState.fileChanges), commandExecutions: turnState.commandExecutions }; - }); + }, { model: options.model }); } export async function findLatestTaskThread(cwd) { From 133f88f1b39aa9399f3ce1d17ac298527c454966 Mon Sep 17 00:00:00 2001 From: hiirott Date: Tue, 7 Jul 2026 09:03:03 +0900 Subject: [PATCH 2/2] fix(app-server): thread model through the shared broker path so --background tasks honor --model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026-06-30 fix (0531356) only covered the direct/fallback client path. The broker path used by task --background dropped options.model at ensureBrokerSession, so the broker's long-lived codex app-server child always started on the CLI default model. Thread model through connect -> ensureBrokerSession -> spawnBrokerProcess -> app-server-broker serve --model -> inner connect. RCA: 根因=broker 子プロセス起動時に model が渡っていなかった(6/30 修正は direct 経路のみ) 再発防止=~/.claude/patches/ registry を4ファイル版に更新・README に broker 経路の注意書き Co-Authored-By: Claude Fable 5 --- plugins/codex/scripts/app-server-broker.mjs | 4 ++-- plugins/codex/scripts/lib/app-server.mjs | 2 +- plugins/codex/scripts/lib/broker-lifecycle.mjs | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/codex/scripts/app-server-broker.mjs b/plugins/codex/scripts/app-server-broker.mjs index 1954274f..7bf940f7 100644 --- a/plugins/codex/scripts/app-server-broker.mjs +++ b/plugins/codex/scripts/app-server-broker.mjs @@ -52,7 +52,7 @@ async function main() { } const { options } = parseArgs(argv, { - valueOptions: ["cwd", "pid-file", "endpoint"] + valueOptions: ["cwd", "pid-file", "endpoint", "model"] }); if (!options.endpoint) { @@ -65,7 +65,7 @@ async function main() { const pidFile = options["pid-file"] ? path.resolve(options["pid-file"]) : null; writePidFile(pidFile); - const appClient = await CodexAppServerClient.connect(cwd, { disableBroker: true }); + const appClient = await CodexAppServerClient.connect(cwd, { disableBroker: true, model: options.model }); let activeRequestSocket = null; let activeStreamSocket = null; let activeStreamThreadIds = null; diff --git a/plugins/codex/scripts/lib/app-server.mjs b/plugins/codex/scripts/lib/app-server.mjs index e6a6e4a7..b3ac39b1 100644 --- a/plugins/codex/scripts/lib/app-server.mjs +++ b/plugins/codex/scripts/lib/app-server.mjs @@ -338,7 +338,7 @@ export class CodexAppServerClient { if (!options.disableBroker) { brokerEndpoint = options.brokerEndpoint ?? options.env?.[BROKER_ENDPOINT_ENV] ?? process.env[BROKER_ENDPOINT_ENV] ?? null; if (!brokerEndpoint) { - const brokerSession = await ensureBrokerSession(cwd, { env: options.env }); + const brokerSession = await ensureBrokerSession(cwd, { env: options.env, model: options.model }); brokerEndpoint = brokerSession?.endpoint ?? null; } } diff --git a/plugins/codex/scripts/lib/broker-lifecycle.mjs b/plugins/codex/scripts/lib/broker-lifecycle.mjs index ef763819..7ebe35d9 100644 --- a/plugins/codex/scripts/lib/broker-lifecycle.mjs +++ b/plugins/codex/scripts/lib/broker-lifecycle.mjs @@ -56,9 +56,13 @@ export async function sendBrokerShutdown(endpoint) { }); } -export function spawnBrokerProcess({ scriptPath, cwd, endpoint, pidFile, logFile, env = process.env }) { +export function spawnBrokerProcess({ scriptPath, cwd, endpoint, pidFile, logFile, model, env = process.env }) { const logFd = fs.openSync(logFile, "a"); - const child = spawn(process.execPath, [scriptPath, "serve", "--endpoint", endpoint, "--cwd", cwd, "--pid-file", pidFile], { + const args = [scriptPath, "serve", "--endpoint", endpoint, "--cwd", cwd, "--pid-file", pidFile]; + if (model) { + args.push("--model", String(model)); + } + const child = spawn(process.execPath, args, { cwd, env, detached: true, @@ -143,6 +147,7 @@ export async function ensureBrokerSession(cwd, options = {}) { endpoint, pidFile, logFile, + model: options.model, env: options.env ?? process.env });