jackwener/OpenCLI · error · CommandExecutionError

Gmail batch-view returned a malformed thread at index ${inde

Error message

Gmail batch-view returned a malformed thread at index ${index}

What it means

Within parseBatchView, each row of body[2] should be a wrapper whose element [0] is a thread record with at least 5 fields (subject, snippet, timestamp, thread id, message list). If the wrapper is not an array, record is null, or record.length < 5, this CommandExecutionError is thrown with the row index. It means Gmail returned a batch-view row the parser cannot read as a thread.

Source

Thrown at clis/gmail/utils.js:141

function senderFromSummary(message) {
  return addressRef(Array.isArray(message) ? message[1] : null);
}

function labelIdsFromMessages(messages) {
  return [...new Set((Array.isArray(messages) ? messages : [])
    .flatMap((message) => Array.isArray(message?.[10]) ? message[10] : [])
    .filter((label) => typeof label === 'string' && label.startsWith('^')))];
}

export function parseBatchView(body) {
  if (!Array.isArray(body) || body.length !== 19) {
    throw new CommandExecutionError('Gmail batch-view response had an unexpected shape');
  }
  const rows = Array.isArray(body[2]) ? body[2] : [];
  return rows.map((wrapper, index) => {
    const record = Array.isArray(wrapper?.[0]) ? wrapper[0] : null;
    if (!record || record.length < 5) {
      throw new CommandExecutionError(`Gmail batch-view returned a malformed thread at index ${index}`);
    }
    const threadId = cleanString(record[3]).replace(/^#/, '');
    const messages = Array.isArray(record[4]) ? record[4] : [];
    const latest = messages.at(-1);
    const sender = senderFromSummary(latest);
    if (!threadId) throw new CommandExecutionError(`Gmail batch-view returned a thread without an id at index ${index}`);
    const labels = labelIdsFromMessages(messages);
    return {
      threadId,
      subject: cleanString(record[0]) || '(no subject)',
      from: sender?.address || null,
      fromName: sender?.name || null,
      snippet: cleanString(record[1]) || null,
      messageCount: messages.length,
      unread: labels.includes('^u'),
      starred: labels.includes('^t'),
      date: gmailDate(record[2], `thread ${threadId}`),
      labels,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Refine the search query to exclude the problematic thread (e.g. add -in:trash or a label filter) and rerun.
  2. Retry; transient placeholder rows during live sync often disappear after Gmail settles.
  3. Update opencli so parseBatchView tolerates/handles the new row shape.
  4. Reload Gmail and rerun to force a fresh batch-view payload.
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isThreadRecord(wrapper) {
  const record = Array.isArray(wrapper?.[0]) ? wrapper[0] : null;
  return Array.isArray(record) && record.length >= 5;
}

Try / catch

try {
  threads = await gmailSearch(query);
} catch (error) {
  const m = String(error.message).match(/malformed thread at index (\d+)/);
  if (m) {
    // rerun with a query that excludes the bad row, or retry after sync settles
    await sleep(1500);
    threads = await gmailSearch(query);
  } else throw error;
}

Prevention

When it happens

Trigger: A /i/bv response containing a row whose record array is missing or shorter than 5 elements — e.g. a placeholder/tombstone row for a deleted or inaccessible thread, or a Gmail payload-format change affecting individual rows.

Common situations: Searches touching recently deleted/undeleting threads, threads in accounts with restricted access, Gmail A/B experiments changing per-row encoding, sync rows for archived/draft placeholders.

Understand the failure class

Related errors


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