{"record":{"id":"f7fd6b4c85f9792e","repo":"jackwener/OpenCLI","slug":"model-name-rawname-is-ambiguous","errorCode":null,"errorMessage":"Model name \"${rawName}\" is ambiguous.","messagePattern":"Model name \"(.+?)\" is ambiguous\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/codex/model.js","lineNumber":56,"sourceCode":"}\n\nfunction extractModelVersion(value) {\n    const match = value.match(/(?:^|\\s)(\\d+(?:\\.\\d+)?)(?=\\s|$)/);\n    return match?.[1] || '';\n}\n\nexport function findUniqueModelOption(labels, rawName) {\n    const name = normalizeModelText(rawName);\n    if (!name) {\n        throw new ArgumentError('model name cannot be empty');\n    }\n    const normalized = labels.map((label) => ({ label, normalized: normalizeModelText(label) }));\n    const exact = normalized.filter(item => item.normalized === name);\n    if (exact.length === 1) {\n        return exact[0].label;\n    }\n    if (exact.length > 1) {\n        throw new CommandExecutionError(`Model name \"${rawName}\" is ambiguous.`, `Matches: ${exact.map(item => item.label).join(', ')}`);\n    }\n    const partial = normalized.filter(item => item.normalized.includes(name));\n    if (partial.length === 1) {\n        return partial[0].label;\n    }\n    if (partial.length > 1) {\n        throw new CommandExecutionError(`Model name \"${rawName}\" is ambiguous.`, `Matches: ${partial.map(item => item.label).join(', ')}`);\n    }\n    return null;\n}\n\nexport function modelSelectionVerified(current, chosen) {\n    const active = normalizeModelText(current);\n    const selected = normalizeModelText(chosen);\n    if (!active || !selected) {\n        return false;\n    }\n    if (active === selected) {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/codex/model.js#L38-L74","documentation":"When resolving the requested model name against the labels scraped from the Codex model menu, `findUniqueModelOption` first filters for exact normalized matches. This CommandExecutionError is thrown when more than one label exactly matches the requested name after normalization (lowercased, 'gpt' prefix stripped, whitespace collapsed), because the library cannot tell which menu item to click. The error detail lists all matching labels.","triggerScenarios":"Running `opencli codex model <name>` when two or more menu items normalize to the same text — e.g., the menu lists both a model variant and a reasoning option whose normalized labels collide, or duplicate entries appear in the opened menu (chat-action items not filtered out).","commonSituations":"Menus polluted with unrelated items (the code filters known chat-action labels, but a Codex update can introduce new ones); asking for a name that matches both a model and a reasoning level (e.g. 'pro'); duplicate model entries rendered by a newer Codex build.","solutions":["Use a more specific name that uniquely identifies one menu item (check the detail line's Matches list).","Run `opencli codex model --list` to see exactly which labels are available and pick a unique substring.","Update opencli if a new Codex version added unfiltered menu items causing false collisions."],"exampleFix":"// before\nopencli codex model \"5\"            // matches GPT-5.5 and GPT-5.4\n// after\nopencli codex model \"5.5\"          # unique exact match\n# or inspect first:\nopencli codex model --list","handlingStrategy":"validation","validationCode":"const listed = await run('opencli codex model --list');\nconst labels = parseLabels(listed);\nconst norm = s => s.toLowerCase().replace(/\\bgpt[-\\s]*/g, '').replace(/\\s+/g, ' ').trim();\nif (labels.filter(l => norm(l) === norm(wanted)).length > 1) {\n  throw new Error(`Ambiguous model \"${wanted}\"; pick from: ${labels.join(', ')}`);\n}","typeGuard":"function uniqueMatch(labels, name) {\n  const n = s => s.toLowerCase().replace(/\\bgpt[-\\s]*/g,'').replace(/\\s+/g,' ').trim();\n  const m = labels.filter(l => n(l) === n(name));\n  return m.length === 1 ? m[0] : null;\n}","tryCatchPattern":"try {\n  await switchModel(name);\n} catch (err) {\n  if (err instanceof CommandExecutionError && /is ambiguous/.test(err.message)) {\n    // parse Matches: list from err.detail and re-invoke with the full exact label\n  } else throw err;\n}","preventionTips":["Use full exact labels from `codex model --list` in scripts","Avoid generic substrings shared by models and reasoning levels ('pro', 'speed')","Keep opencli's chat-action label filter current for new Codex menus","Prefer exact version strings ('5.5') over bare prefixes ('5')"],"tags":["ambiguity","ui-scraping","codex","model-selection"],"backgroundTag":"ambiguous-model-name","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}