You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Four controls decide which CLI a coding session launches, and the one the console calls "Coding CLI" is read by nothing — switching to Codex kept burning the Claude session limit #549
"hit the session limit again even after the user thought they had switched to Codex… there are two separate settings… changing only one was not enough."
There are four, not two, and the one the console labels "Coding CLI" — the one on the Settings tab, under the heading "Agent settings" — is read by no code at all.
Verified: the setting has zero readers
It is live on every Repo Coder. GET /v1/agents/agent_coder_repo/settings-schema, production, 2026-08-13:
{ "id": "engine", "label": "Coding CLI", "type": "select",
"description": "Which CLI this agent drives on your machine.",
"options": [ {"value":"claude","label":"Claude Code"},
{"value":"codex","label":"Codex"},
{"value":"grok","label":"Grok"} ],
"default": "claude" }
It is rendered by store/console/src/tabs/SettingsTab.tsx:442-445 ("Agent settings"), stored on agent_instances.config.settings.engine, and:
Nothing reads it. Its only effect is that agent-think.ts injects it into the chat prompt's ## Settings block, so the agent is told its engine is Codex while the runner spawns something else.
The four controls that DO decide, in the order they win
workers/api/src/routes/coding.ts:136-146:
// One active session per repo — a second would share the repo's single working// directory and conflict … Reuse the live one.constexisting=awaitgetActiveSessionForRepo(c.env,instanceId,uid,repoId);if(existing){construnnerConnected=(awaitstartSessionOnRunner(c.env,instanceId,uid,existing,repo)).conn!=null;returnc.json({session: existing, runnerConnected,reused: true},200);}const{ command, clientType }=awaitresolveEngine(c.env,instanceId,uid,body.engineId??body.clientType??repo.defaultClient);
everything — the reuse branch returns before resolveEngine is ever called
2
coding_repos.default_client
createRepo, defaulting to "claude" (coding-store.ts:192)
3 and 4
3
agent_instances.config.defaultEngineId
console ⚙ CLI engines panel
4
4
agent_instances.config.settings.engine
console Settings → Agent settings → "Coding CLI"
nothing
So the visible, obvious, agent-named control is last and inert, and the control that actually decides is #1 — a value frozen on a session that the console's "start a session" button silently reuses. A user who changes any setting and clicks start gets {"reused": true} and the old engine, with no message saying so.
Current state, measured — the controls agree today, so this is latent
GET …/settings and GET …/coding/engines for all nine of this owner's Repo Coder instances, 2026-08-13:
AIPA coder setting.engine=codex defaultEngineId=codex codex exec --sandbox danger-full-access
Heartfull setting.engine=claude defaultEngineId=claude claude --dangerously-skip-permissions
Chess coder 2 setting.engine=claude defaultEngineId=claude claude --dangerously-skip-permissions
FIS coder / Chess coder / PAS Coder / FWS platform / FGS platform / FAS platform — all claude/claude
No divergence right now (he evidently fixed both by hand after the incident). The mechanism that produced the incident is untouched, and nothing keeps the four in sync or reports a disagreement.
This is the same defect the platform already fixed once
Migration 0102_coder_repo_drop_repo_setting.sql deleted the Repo Coder's repo setting for exactly this reason, and its header reads as if it were written about engine:
"Which repo does this agent work on" was written down in two places. … The reported bug was "I updated it, but it is still using the old one", and both halves were true. … Half a wire is worse than none: it makes the field look connected.
The repo setting had half a wire (attachSettingRepo, create-only). engine has none. It survived that cleanup, and 0092_coder_repo_engine_copy.sql — which touched this exact field — only corrected its description ("in tmux" → "on your machine") while leaving it inert.
What to do, cheapest first
Delete the engine field from the Repo Coder's settingsSchema, by migration, the way 0102 did for repo. One UPDATE agents. Stored values orphan harmlessly (they already do nothing) and the console stops rendering it. This alone removes the control that caused the reported loss.
Tell the user when a session was reused.coding.ts:139-142 already returns reused: true and the console ignores it. If the reused session's launch_command does not match what resolveEnginewould have picked, say so: "Reusing the live Claude Code session on this repo. End it to start a Codex one." This is the half that actually bit him — even with one control, a live session outlives the change.
Then decide where "which CLI" lives — repo, or instance. Today coding_repos.default_client silently outranks config.defaultEngineId, so the ⚙ CLI engines default is itself only sometimes honoured. Making the repo settings sheet ([bug] A repo's folder cannot be changed — the console shows it as read-only text and the update API has no parameter for it, so a wrong path can only be deleted #410) the single home matches 0102's conclusion that a repo's facts live on the repo row; the alternative is dropping repo.defaultClient from the ?? chain. I would put it on the repo, because the engine is a property of the codebase (Codex for one repo, Claude for another) far more often than of the agent. Owner's call; either way it must be one place.
Alternatives considered and rejected
Wire settings.engine through to resolveEngine. Rejected — that is the mirror image of 0102's reasoning. It makes a typed settings field mutate coding_repos/coding_sessions state, adds a fifth writer, and creates the sync that 0102 says is "a disagreement waiting to happen".
Make the settings field authoritative and delete the ⚙ panel. Rejected: the panel edits {id,label,command} presets — the actual command line, flags included (codex exec --sandbox danger-full-access). A three-option select cannot express that, and coding-engines.ts:232-247 documents why the write-flag per engine matters.
Auto-restart the session when the engine choice changes. Rejected: ending a live coding session out from under a running turn to honour a settings save is a destructive side effect of a config write. Tell the user; let them choose.
Acceptance criteria
GET /v1/agents/agent_coder_repo/settings-schema no longer contains an engine field.
Starting a session on a repo with a live session returns a message the console shows, naming the engine that is actually running and how to change it.
A test asserts that no field id in any seeded settingsSchema is also a key that resolveEngine/resolveEngineEnv consults — the general form of this bug, and the one seed-drift.test.ts is already positioned to carry.
Regression risk
Removing a settingsSchema field is proven safe by 0102, with the same orphaning behaviour. The real risk is in step 3: repo.defaultClient defaults to "claude" on every repo created (coding-store.ts:192), so dropping it from the ?? chain would silently move every existing repo onto the instance default. If that path is taken, migrate default_client into defaultEngineId first, or scope the change to repos where the two disagree.
Verified from source and from production reads of the live schema, settings and engine presets. Not verified: that the historical Codex/Claude divergence was #4-vs-#3 rather than #1-vs-#3 — the settings have since been aligned by hand and neither carries an audit trail. Both paths are open and both are fixed by the steps above.
Related: #411 / #520 / migration 0102 (the same defect for repo), #92 (edited this field's description without noticing it was inert), #248 (which credential a session actually used).
The owner's report
There are four, not two, and the one the console labels "Coding CLI" — the one on the Settings tab, under the heading "Agent settings" — is read by no code at all.
Verified: the setting has zero readers
It is live on every Repo Coder.
GET /v1/agents/agent_coder_repo/settings-schema, production, 2026-08-13:{ "id": "engine", "label": "Coding CLI", "type": "select", "description": "Which CLI this agent drives on your machine.", "options": [ {"value":"claude","label":"Claude Code"}, {"value":"codex","label":"Codex"}, {"value":"grok","label":"Grok"} ], "default": "claude" }It is rendered by
store/console/src/tabs/SettingsTab.tsx:442-445("Agent settings"), stored onagent_instances.config.settings.engine, and:Nothing reads it. Its only effect is that
agent-think.tsinjects it into the chat prompt's## Settingsblock, so the agent is told its engine is Codex while the runner spawns something else.The four controls that DO decide, in the order they win
workers/api/src/routes/coding.ts:136-146:and
lib/coding-engines.ts:318-320:coding_sessions.launch_commandresolveEngineis ever calledcoding_repos.default_clientcreateRepo, defaulting to"claude"(coding-store.ts:192)agent_instances.config.defaultEngineIdagent_instances.config.settings.engineSo the visible, obvious, agent-named control is last and inert, and the control that actually decides is #1 — a value frozen on a session that the console's "start a session" button silently reuses. A user who changes any setting and clicks start gets
{"reused": true}and the old engine, with no message saying so.Current state, measured — the controls agree today, so this is latent
GET …/settingsandGET …/coding/enginesfor all nine of this owner's Repo Coder instances, 2026-08-13:No divergence right now (he evidently fixed both by hand after the incident). The mechanism that produced the incident is untouched, and nothing keeps the four in sync or reports a disagreement.
This is the same defect the platform already fixed once
Migration
0102_coder_repo_drop_repo_setting.sqldeleted the Repo Coder'sreposetting for exactly this reason, and its header reads as if it were written aboutengine:The
reposetting had half a wire (attachSettingRepo, create-only).enginehas none. It survived that cleanup, and0092_coder_repo_engine_copy.sql— which touched this exact field — only corrected its description ("in tmux" → "on your machine") while leaving it inert.What to do, cheapest first
enginefield from the Repo Coder'ssettingsSchema, by migration, the way0102did forrepo. OneUPDATE agents. Stored values orphan harmlessly (they already do nothing) and the console stops rendering it. This alone removes the control that caused the reported loss.coding.ts:139-142already returnsreused: trueand the console ignores it. If the reused session'slaunch_commanddoes not match whatresolveEnginewould have picked, say so: "Reusing the live Claude Code session on this repo. End it to start a Codex one." This is the half that actually bit him — even with one control, a live session outlives the change.coding_repos.default_clientsilently outranksconfig.defaultEngineId, so the ⚙ CLI engines default is itself only sometimes honoured. Making the repo settings sheet ([bug] A repo's folder cannot be changed — the console shows it as read-only text and the update API has no parameter for it, so a wrong path can only be deleted #410) the single home matches0102's conclusion that a repo's facts live on the repo row; the alternative is droppingrepo.defaultClientfrom the??chain. I would put it on the repo, because the engine is a property of the codebase (Codex for one repo, Claude for another) far more often than of the agent. Owner's call; either way it must be one place.Alternatives considered and rejected
settings.enginethrough toresolveEngine. Rejected — that is the mirror image of0102's reasoning. It makes a typed settings field mutatecoding_repos/coding_sessionsstate, adds a fifth writer, and creates the sync that0102says is "a disagreement waiting to happen".{id,label,command}presets — the actual command line, flags included (codex exec --sandbox danger-full-access). A three-option select cannot express that, andcoding-engines.ts:232-247documents why the write-flag per engine matters.Acceptance criteria
GET /v1/agents/agent_coder_repo/settings-schemano longer contains anenginefield.settingsSchemais also a key thatresolveEngine/resolveEngineEnvconsults — the general form of this bug, and the oneseed-drift.test.tsis already positioned to carry.Regression risk
Removing a
settingsSchemafield is proven safe by0102, with the same orphaning behaviour. The real risk is in step 3:repo.defaultClientdefaults to"claude"on every repo created (coding-store.ts:192), so dropping it from the??chain would silently move every existing repo onto the instance default. If that path is taken, migratedefault_clientintodefaultEngineIdfirst, or scope the change to repos where the two disagree.Verified from source and from production reads of the live schema, settings and engine presets. Not verified: that the historical Codex/Claude divergence was #4-vs-#3 rather than #1-vs-#3 — the settings have since been aligned by hand and neither carries an audit trail. Both paths are open and both are fixed by the steps above.
Related: #411 / #520 / migration 0102 (the same defect for
repo), #92 (edited this field's description without noticing it was inert), #248 (which credential a session actually used).