jackwener/OpenCLI · error · CommandExecutionError
Codex picker option disappeared before it could be clicked.
Error message
Codex picker option disappeared before it could be clicked.
What it means
This error is thrown when a Codex slash-command picker option that was found in an earlier evaluation pass disappears from the DOM before the deferred, injected click can execute. The library clicks picker options via an in-page script that re-locates the element at click time; if that re-lookup fails, `clicked` is not `true` and this error is raised, aborting the send so a stale click cannot target the wrong option.
Source
Thrown at clis/codex/send.js:187
bubbles: true, cancelable: true, button: 0, buttons: 1,
clientX: Math.round(rect.left + rect.width / 2),
clientY: Math.round(rect.top + rect.height / 2),
};
// The picker only responds to the full pointer chain, and deferring it
// keeps the eval response ahead of the re-render that closes the picker.
Promise.resolve().then(() => {
try {
target.dispatchEvent(new PointerEvent('pointerdown', { ...init, pointerType: 'mouse' }));
target.dispatchEvent(new MouseEvent('mousedown', init));
target.dispatchEvent(new PointerEvent('pointerup', { ...init, pointerType: 'mouse' }));
target.dispatchEvent(new MouseEvent('mouseup', init));
target.dispatchEvent(new MouseEvent('click', init));
} catch {}
});
return true;
})()`));
if (clicked !== true) {
throw new CommandExecutionError('Codex picker option disappeared before it could be clicked.', `wanted=${picked.title}`);
}
// The click above is deferred, so verify it landed: the picker must
// close and the composer must hold a chip for the picked option.
let applied = null;
for (let attempt = 0; attempt < 20; attempt += 1) {
const state = await readComposerState(page);
const pickerOpen = unwrapEvaluateResult(await page.evaluate(`Array.from(document.querySelectorAll('${PICKER_ITEM_SELECTOR}')).some((it) => it instanceof HTMLElement && !!it.offsetParent)`)) === true;
if (!pickerOpen && state.chips.some((chip) => chipMatchesLabel(chip, picked.title))) {
applied = state;
break;
}
applied = state;
await page.wait(0.1);
}
if (!applied || !applied.chips.some((chip) => chipMatchesLabel(chip, picked.title))) {
throw new CommandExecutionError(
'Codex picker selection did not reach the composer.',
`expected=${picked.title} ${describeComposerState(applied ?? { text: '', chips: [] })}`,View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run the command; the error is timing-related and a retry usually finds the picker stable.
- Ensure no other automation (keyboard input, Escape presses) dismisses the picker concurrently.
- Slow down or run against a less loaded Codex page so re-renders don't race the click.
- Update the library/Codex if the picker DOM structure changed and re-lookup selectors no longer match.
Example fix
// before (racing click)
const clicked = await page.evaluate(`(async () => { ... locate + click ... })()`);
// after (verify picker still open right before clicking, e.g. re-run with a small stabilization wait)
await page.wait(0.2);
const clicked = await page.evaluate(`(async () => { ... re-locate option; if (!option) return false; option.dispatchEvent(new MouseEvent('click', init)); return true; })()`); Defensive patterns
Strategy: retry
Validate before calling
// Before invoking send, confirm the picker is open and the option exists
const optionPresent = await page.evaluate(`!!document.querySelector('[role="menu"] [role="menuitem"], [role="listbox"] [role="option"]')`);
if (!optionPresent) { /* reopen picker or abort before calling send */ } Type guard
const isClickResult = (v) => v === true;
Try / catch
try {
await codexSend({ pick: label });
} catch (e) {
if (String(e.message).includes('picker option disappeared')) {
await page.pressKey('Escape');
await retry(() => codexSend({ pick: label }), { attempts: 3, backoff: 250 });
} else throw e;
} Prevention
- Avoid concurrent automation that can dismiss the picker mid-click.
- Run against a settled, fully loaded Codex page.
- Retry once or twice — the failure is a timing race.
- Keep the library updated after Codex UI changes.
When it happens
Trigger: The in-page click script returned a value other than `true` — the picker option element matching `picked.title` was removed, re-rendered, or the picker closed between the option-discovery pass and the deferred click (e.g. Codex re-rendered the dropdown, an animation removed the row, or the DOM mutated in between).
Common situations: Slow or busy Codex session where the picker auto-closes; Codex UI updates that re-render the picker list mid-click; running the send while another automation step already dismissed the picker; flaky DOM timing on a heavily loaded page.
Related errors
- Codex picker selection did not reach the composer.
- --pick label cannot be empty
- Picker option "${rawLabel}" is ambiguous.
- Codex picker did not open after typing the command.
- No picker option matched.
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/d7d80e3d0746bbf2.
Report an issue: GitHub.