jackwener/OpenCLI · error · Error

Empty user message

Error message

Empty user message

What it means

After finding the last user message, handleMessages runs extractTextContent on its content and throws 'Empty user message' when the resulting text is whitespace-only. A user-role message exists but carries no usable text (e.g. image-only or empty content parts), so there is nothing to type into the Antigravity input.

Source

Thrown at clis/antigravity/serve.js:351

            }
            throw err;
        }
        await sleep(pollInterval);
    }
    throw new Error(`Timeout waiting for Antigravity reply after ${timeout / 1000}s`);
}
// ─── Request Handlers ────────────────────────────────────────────────
async function handleMessages(body, page, opts = {}) {
    const { bridge, timeout, reconnect } = opts;
    // Extract the last user message
    const userMessages = body.messages.filter(m => m.role === 'user');
    if (userMessages.length === 0) {
        throw new Error('No user message found in request');
    }
    const lastUserMsg = userMessages[userMessages.length - 1];
    const userText = extractTextContent(lastUserMsg.content);
    if (!userText.trim()) {
        throw new Error('Empty user message');
    }
    // Optimization 1: New conversation if this is the first message in the session
    if (body.messages.length === 1) {
        console.error(`[serve] New session detected (1 message). Starting new conversation in UI.`);
        await startNewConversation(page);
    }
    // Optimization 3: Switch model if requested
    if (body.model) {
        await switchModel(page, body.model);
    }
    // Get conversation state before sending
    const beforeText = await getConversationText(page);
    // Send the message
    console.error(`[serve] Sending: "${userText.slice(0, 80)}${userText.length > 80 ? '...' : ''}"`);
    await sendMessage(page, userText, bridge);
    // Poll for reply (change detection)
    console.error('[serve] Waiting for reply...');
    page = await waitForReply(page, beforeText, { timeout, reconnect });

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Send non-empty text in the user message content
  2. Inspect how extractTextContent handles content arrays and include a text part
  3. Fix client templating so the prompt text is actually populated
  4. Reject empty prompts client-side before calling the server

Example fix

// before
{ role: 'user', content: [{ type: 'image_url', image_url: { url: '...' } }] }
// after
{ role: 'user', content: [{ type: 'text', text: 'Describe this image' }, { type: 'image_url', image_url: { url: '...' } }] }
Defensive patterns

Strategy: validation

Validate before calling

const text = lastUserMsg.content;
const hasText = typeof text === 'string' ? text.trim().length > 0
  : Array.isArray(text) && text.some(p => p?.type === 'text' && p.text?.trim());
if (!hasText) throw new Error('User message must include non-empty text');

Type guard

function hasNonEmptyText(msg) {
  const c = msg?.content;
  return (typeof c === 'string' && c.trim().length > 0)
    || (Array.isArray(c) && c.some(p => typeof p?.text === 'string' && p.text.trim().length > 0));
}

Try / catch

try {
  const reply = await sendToAntigravity(body);
} catch (err) {
  if (err.message === 'Empty user message') {
    console.error('User content must contain text, got:', JSON.stringify(body.messages.at(-1)?.content));
  }
  throw err;
}

Prevention

When it happens

Trigger: user message content is an empty string, whitespace, an empty parts array, or extractTextContent drops non-text parts leaving ''.

Common situations: Multimodal clients sending only an image attachment the CLI can't type; templating bug leaving the user content blank; content as a parts array with only unsupported types; client sending role 'user' with content null.

Related errors


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