jackwener/OpenCLI · error · CommandExecutionError

Failed to insert text into Gemini composer

Error message

Failed to insert text into Gemini composer

What it means

Thrown by sendGeminiMessage when, after trying native typing (page.nativeType) and the in-page insertComposerTextFallbackScript, the composer still reports no text (hasText false). The library refuses to submit an empty message, so it aborts with this CommandExecutionError.

Source

Thrown at clis/gemini/utils.js:1317

    if (!prepared?.ok) {
        throw new CommandExecutionError(prepared?.reason || 'Could not find Gemini composer');
    }
    let hasText = false;
    if (page.nativeType) {
        try {
            await page.nativeType(text);
            await page.wait(0.2);
            const nativeState = await page.evaluate(composerHasTextScript());
            hasText = !!nativeState?.hasText;
        }
        catch { }
    }
    if (!hasText) {
        const fallbackState = await page.evaluate(insertComposerTextFallbackScript(text));
        hasText = !!fallbackState?.hasText;
    }
    if (!hasText) {
        throw new CommandExecutionError('Failed to insert text into Gemini composer');
    }
    const submitAction = await page.evaluate(submitComposerScript());
    if (submitAction === 'button') {
        await page.wait(1);
        return 'button';
    }
    if (page.nativeKeyPress) {
        try {
            await page.nativeKeyPress('Enter');
        }
        catch {
            await page.evaluate(dispatchComposerEnterScript());
        }
    }
    else {
        await page.evaluate(dispatchComposerEnterScript());
    }
    await page.wait(1);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry sendGeminiMessage — transient re-render clearing input is the most common cause.
  2. Check that the message doesn't exceed Gemini's input limits; try a short test string.
  3. Verify page.nativeType works in your automation backend (permissions, focus) or rely on the fallback path.
  4. Inspect insertComposerTextFallbackScript's target selectors against the current composer DOM and update if Gemini changed markup.
  5. Before sending, focus the composer explicitly and dismiss any overlay that might steal focus.

Example fix

// before
await sendGeminiMessage(page, veryLongText);
// after
await sendGeminiMessage(page, veryLongText.slice(0, 5000)); // stay under composer limits
Defensive patterns

Strategy: fallback

Validate before calling

// verify typing registered before submitting
const hasText = await page.evaluate(`(() => { const el = document.querySelector('rich-textarea, [contenteditable="true"]'); return !!el && !!(el.textContent || '').trim(); })()`);
if (!hasText) console.warn('composer empty before submit; aborting send');

Try / catch

try {
  await sendGeminiMessage(page, text);
} catch (e) {
  if (String(e.message).includes('Failed to insert text')) {
    await page.wait(1); // let composer re-render settle
    await sendGeminiMessage(page, text.slice(0, 4000)); // retry, shorter payload
  } else throw e;
}

Prevention

When it happens

Trigger: nativeType silently fails (e.g. focus stolen, composer is a shadow-DOM/contenteditable the driver can't type into) AND the DOM-insertion fallback also fails to register text — e.g. the composer re-rendered and cleared input, the target element is readonly/disabled, or composerHasTextScript checks a different node than the one typed into.

Common situations: Very long messages hitting input length limits; Gemini swapping the composer element mid-type (re-render); clipboard/keyboard permissions missing in the automation context; UI updates changing the contenteditable structure so both typing and the fallback miss the real input.

Related errors


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