jackwener/OpenCLI · error · CommandExecutionError

Gmail fetch-data returned a malformed message

Error message

Gmail fetch-data returned a malformed message

What it means

Inside parseFetchData, each message wrapper must yield a non-empty threadId (wrapper[0]), a non-empty messageId (wrapper[0] of the row), and a record array at wrapper[1]. If any of these is missing this CommandExecutionError is thrown. Without these keys the message cannot be addressed or rendered, so the library refuses the whole payload rather than returning partial data.

Source

Thrown at clis/gmail/utils.js:253

      size: Number.isFinite(Number(data[4])) ? Number(data[4]) : null,
    });
  }
  return out;
}

export function parseFetchData(body) {
  if (!Array.isArray(body) || !Array.isArray(body[1])) {
    throw new CommandExecutionError('Gmail fetch-data response had an unexpected shape');
  }
  const messages = [];
  for (const threadWrapper of body[1]) {
    const threadId = cleanString(threadWrapper?.[0]).replace(/^#/, '');
    const rows = Array.isArray(threadWrapper?.[2]) ? threadWrapper[2] : [];
    for (const wrapper of rows) {
      const messageId = cleanString(wrapper?.[0]).replace(/^#/, '');
      const record = Array.isArray(wrapper?.[1]) ? wrapper[1] : null;
      if (!threadId || !messageId || !record) {
        throw new CommandExecutionError('Gmail fetch-data returned a malformed message');
      }
      const from = senderFromRecord(record);
      const attachments = parseAttachments(record);
      messages.push({
        messageId,
        legacyMessageId: cleanString(record[34]) || null,
        threadId,
        subject: cleanString(record[4]) || '(no subject)',
        from: from?.address || null,
        fromName: from?.name || null,
        to: addressList(record[0]).map((item) => item.address).join(', ') || null,
        cc: addressList(record[1]).map((item) => item.address).join(', ') || null,
        date: gmailDate(record[16], `message ${messageId}`),
        snippet: cleanString(record[6]) || null,
        body: messageBody(record) || null,
        attachments,
      });
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry after letting the thread fully render in the browser — mid-load captures cause empty ids.
  2. Update opencli if Gmail moved message record fields.
  3. Reload Gmail and reopen the thread, then rerun the command.
  4. Fetch the thread again excluding it from bulk operations if it is being modified concurrently.
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

function isMessageWrapper(row) {
  const id = typeof row?.[0] === 'string' ? row[0].replace(/^#/, '').trim() : '';
  return id.length > 0 && Array.isArray(row?.[1]);
}

Try / catch

try {
  messages = await gmailThread(threadId);
} catch (error) {
  if (String(error.message).includes('malformed message')) {
    await sleep(2000); // allow Gmail to finish hydrating the thread
    messages = await gmailThread(threadId);
  } else throw error;
}

Prevention

When it happens

Trigger: A /i/fd response containing a row whose message id is empty, whose record array is absent, or whose enclosing thread wrapper lacks a thread id — placeholder rows while the thread is streaming in, or a Gmail field-layout change.

Common situations: Opening a thread while Gmail is still hydrating it, messages being deleted/expunged concurrently, Gmail A/B rollouts changing per-message record positions, very large threads partially synced.

Understand the failure class

Related errors


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