jackwener/OpenCLI · error · CommandExecutionError

Codex picker selection did not reach the composer.

Error message

Codex picker selection did not reach the composer.

What it means

After the picker option click succeeds, the library polls `readComposerState` up to 20 times to confirm the composer now holds a chip matching the picked option. If no state shows a matching chip (`applied` is null or `chipMatchesLabel` fails), the selection silently failed and this error is thrown, preventing a send of the wrong content.

Source

Thrown at clis/codex/send.js:203

    })()`));
            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: [] })}`,
                );
            }
        }
        await page.pressKey('Enter');
        let state = await pollComposerState(page);
        if (state.text) {
            // The picker consumes Enter to insert the highlighted entry, so a
            // still-loaded composer is either the untouched draft or the chip
            // the picker resolved. Anything else was rewritten: never submit.
            const slashToken = /^\/\S+$/.test(textToInsert) ? textToInsert.slice(1) : '';
            const chipLabel = picked ? picked.title : slashToken;
            const unchangedDraft = state.chips.length === 0 && state.text === String(textToInsert).trim();
            const pickerResolvedCommand = chipLabel !== ''
                && state.chips.length === 1
                && state.nonChipText === ''
                && chipMatchesLabel(state.chips[0], chipLabel);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the send; transient click/apply races usually resolve on retry.
  2. Check that the `--pick <label>` text matches the picker option label exactly (case/spacing).
  3. Press Escape in Codex to clear any half-applied state, then retry.
  4. If it persists after a Codex update, the chip rendering changed — update or patch `chipMatchesLabel`.

Example fix

// before
await runCodexSend({ pick: 'Plan Review' }); // label mismatch
// after
await runCodexSend({ pick: 'Plan review' }); // exact picker label
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the pick label matches an existing picker option before sending
const labels = await readPickerOptionLabels(page);
if (!labels.includes(pickLabel)) throw new Error(`Pick label '${pickLabel}' not in options: ${labels.join(', ')}`);

Type guard

const isAppliedState = (s) => s != null && Array.isArray(s.chips) && s.chips.some((c) => chipMatchesLabel(c, expectedTitle));

Try / catch

try {
  await codexSend({ pick: label });
} catch (e) {
  if (String(e.message).includes('did not reach the composer')) {
    await page.pressKey('Escape'); // reset composer
    // verify label then retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Clicking a picker option did not insert its chip into the composer within the polling window — the click landed on a non-interactive overlay, the chip label differs from `picked.title`, or the picker was closed without applying the selection.

Common situations: Codex UI version changed so chips render differently or labels are truncated differently; click hit an overlay instead of the option; picker selection applied to a different composer than the one being polled.

Related errors


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