jackwener/OpenCLI · error · CommandExecutionError
Codex model selection was inconsistent.
Error message
Codex model selection was inconsistent.
What it means
As a consistency check, the `codex model` command compares the label chosen by the in-page script (`result.chosen`) with the label independently resolved by `findUniqueModelOption` (`selected`). This CommandExecutionError is thrown when they differ, meaning the two matching passes picked different menu items for the same requested name — a sign of nondeterministic or divergent matching logic. A switch should never proceed on that basis.
Source
Thrown at clis/codex/model.js:235
chosen.dispatchEvent(new MouseEvent('mouseup', cinit));
chosen.dispatchEvent(new MouseEvent('click', cinit));
} catch {}
});
return { ok: true, switched: true, chosen: chosenLabel, labels };
})()`));
if (!result.ok) {
throw new CommandExecutionError(result.reason, result.detail || '');
}
if (listOnly) {
return result.labels.map((m) => ({ Status: m === current ? 'Active' : 'Available', Model: m }));
}
const selected = findUniqueModelOption(result.labels || [], name);
if (!selected) {
throw new CommandExecutionError('No model matched.', `wanted=${name} available=${JSON.stringify(result.labels || [])}`);
}
if (selected !== result.chosen) {
throw new CommandExecutionError('Codex model selection was inconsistent.', `expected=${selected} chosen=${result.chosen}`);
}
let verified = '';
for (let attempt = 0; attempt < 20; attempt += 1) {
await page.wait(0.25);
const reread = unwrapEvaluateResult(await page.evaluate(`(function() {
const re = new RegExp(${patternJson});
const composers = Array.from(document.querySelectorAll('[contenteditable="true"]')).filter((el) => el.offsetParent);
const last = composers[composers.length - 1];
if (!last) return '';
let root = last;
for (let i = 0; i < 5; i++) root = root.parentElement || root;
const btns = Array.from(root.querySelectorAll('button')).filter((b) => b.offsetParent);
const match = btns.find((b) => re.test((b.textContent || '').trim()));
return match ? (match.textContent || '').trim() : '';
})()`));
if (modelSelectionVerified(reread, selected)) {
verified = reread;
break;View on GitHub (pinned to 49907e53dc)
Solutions
- Retry with the exact full label from `opencli codex model --list` so both passes match the same item uniquely.
- Compare expected= vs chosen= in the error detail to see which two labels diverged, then choose a name unique to the intended one.
- Update opencli so the in-page matching normalization matches findUniqueModelOption exactly.
- If it persists on a specific label, work around by switching via an unambiguous alternative (full version string) and report the drift.
Example fix
// before opencli codex model "speed" // page script picks 'Speed ⌘1', node picks 'Speed pro' // after opencli codex model --list opencli codex model "Speed" # exact unique label so both passes agree
Defensive patterns
Strategy: validation
Validate before calling
const listed = await run('opencli codex model --list');
const labels = parseLabels(listed);
// ensure exactly one unique match so both matching passes agree
const norm = s => s.toLowerCase().replace(/\bgpt[-\s]*/g,'').replace(/\s+/g,' ').trim();
const hits = labels.filter(l => norm(l) === norm(wanted));
if (hits.length !== 1) throw new Error('Provide one exact unique label'); Type guard
function uniquelyResolvable(labels, name) {
const n = s => s.toLowerCase().replace(/\bgpt[-\s]*/g,'').replace(/\s+/g,' ').trim();
const exact = labels.filter(l => n(l) === n(name));
if (exact.length === 1) return exact[0];
const partial = labels.filter(l => n(l).includes(n(name)));
return partial.length === 1 ? partial[0] : null;
} Try / catch
try {
await switchModel(name);
} catch (err) {
if (err instanceof CommandExecutionError && /inconsistent/.test(err.message)) {
// parse expected=/chosen= from detail, retry with the expected label
} else throw err;
} Prevention
- Use exact full labels so the in-page and Node matching passes pick the same item
- Avoid labels with badges/shortcuts that normalize differently in each pass
- Keep opencli's two matching implementations in sync after Codex updates
- Retry the switch once when it reproduces intermittently
When it happens
Trigger: Running `opencli codex model <name>` where the page script's substring matching (lines 184-201) and the Node-side findUniqueModelOption normalization disagree — e.g., subtle normalization differences (whitespace, 'gpt' stripping, trailing modifiers after kbd removal) cause each pass to select a different label among near-duplicate entries.
Common situations: Menu containing near-identical labels differing only in whitespace or badges; a Codex update changing label text between scrape and re-match; labels with keyboard-shortcut remnants altering the page-script match but not the Node match (or vice versa).
Related errors
- Model name "${rawName}" is ambiguous.
- ${result.reason}
- Codex extract-diff returned an invalid payload.
- No Codex diffs were visible. Run opencli codex send "/review
- No Codex conversations were visible. Open the Codex sidebar
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/ad34fbffec7a7ef0.
Report an issue: GitHub.