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

  1. Retry with the exact full label from `opencli codex model --list` so both passes match the same item uniquely.
  2. Compare expected= vs chosen= in the error detail to see which two labels diverged, then choose a name unique to the intended one.
  3. Update opencli so the in-page matching normalization matches findUniqueModelOption exactly.
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/ad34fbffec7a7ef0. Report an issue: GitHub.