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

  1. Re-run the command; the error is timing-related and a retry usually finds the picker stable.
  2. Ensure no other automation (keyboard input, Escape presses) dismisses the picker concurrently.
  3. Slow down or run against a less loaded Codex page so re-renders don't race the click.
  4. 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

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


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