jackwener/OpenCLI · error · CommandExecutionError

Gemini visible turns returned a malformed row

Error message

Gemini visible turns returned a malformed row

What it means

Thrown by getGeminiStructuredTurns when the getTurnsScript page.evaluate result contains a row that is not an object with string Role and Text fields. The library strictly validates every visible turn before collapsing adjacent turns, so any malformed turn aborts the whole transcript read.

Source

Thrown at clis/gemini/utils.js:1191

        return false;
    const clicked = await page.evaluate(clickGeminiConversationByTitleScript(normalizedQuery));
    if (clicked)
        await page.wait(1);
    return !!clicked;
}
export async function getGeminiVisibleTurns(page) {
    const turns = await getGeminiStructuredTurns(page);
    if (Array.isArray(turns) && turns.length > 0)
        return turns;
    const lines = await getGeminiTranscriptLines(page);
    return lines.map((line) => ({ Role: 'System', Text: line }));
}
async function getGeminiStructuredTurns(page) {
    await ensureGeminiPage(page);
    const raw = requireGeminiArrayResult(await page.evaluate(getTurnsScript()), 'Gemini visible turns');
    for (const turn of raw) {
        if (!isObjectRecord(turn) || typeof turn.Role !== 'string' || typeof turn.Text !== 'string') {
            throw new CommandExecutionError('Gemini visible turns returned a malformed row');
        }
    }
    const turns = collapseAdjacentGeminiTurns(raw);
    return Array.isArray(turns) ? turns : [];
}
export async function getGeminiTranscriptLines(page) {
    await ensureGeminiPage(page);
    const lines = requireGeminiArrayResult(await page.evaluate(getTranscriptLinesScript()), 'Gemini transcript lines');
    if (!lines.every((line) => typeof line === 'string')) {
        throw new CommandExecutionError('Gemini transcript lines returned a malformed row');
    }
    return lines;
}
export async function waitForGeminiTranscript(page, attempts = 5) {
    let lines = [];
    for (let index = 0; index < attempts; index += 1) {
        lines = await getGeminiTranscriptLines(page);
        if (lines.length > 0)

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the read after a short wait so the conversation DOM settles.
  2. Log/dump the offending raw turn from getTurnsScript and update its selectors to the current DOM.
  3. Skip non-conforming turns inside the in-page script instead of returning them.
  4. Catch CommandExecutionError and fall back to getGeminiTranscriptLines (plain strings) for a less strict representation.
Defensive patterns

Strategy: try-catch

Type guard

function isGeminiTurn(t) {
  return typeof t === 'object' && t !== null &&
    typeof t.Role === 'string' && typeof t.Text === 'string';
}

Try / catch

try {
  turns = await getGeminiStructuredTurns(page);
} catch (e) {
  if (String(e.message).includes('malformed row')) {
    lines = await getGeminiTranscriptLines(page); // fallback representation
  } else throw e;
}

Prevention

When it happens

Trigger: Reading the transcript when the visible-turns extraction returns entries like { Role: null }, missing Text, or non-object items — typically because Gemini's conversation DOM changed (new turn wrapper elements, embedded widgets, code blocks) or because the page was still rendering while polled.

Common situations: Gemini UI update altering turn containers; responses containing special blocks (images, tools, thinking panels) that the extraction script mis-shapes; reading a half-rendered streaming response; locale variants with different markup.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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