jackwener/OpenCLI · error · CommandExecutionError

Gmail batch-view returned a thread without an id at index ${

Error message

Gmail batch-view returned a thread without an id at index ${index}

What it means

parseBatchView extracts the thread id from record[3] (stripping a leading '#'); if the cleaned value is empty it throws this CommandExecutionError naming the row index. A thread record without an id cannot be addressed by later commands (open/reply), so the library refuses to return a row it cannot key. This usually means the record's field layout shifted or the row is a placeholder.

Source

Thrown at clis/gmail/utils.js:147

    .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,
    };
  });
}

function labelCounts(value) {
  const result = new Map();

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command — concurrent edits to threads during the search can produce transient id-less rows.
  2. Update opencli to match the current Gmail record layout if the error is reproducible.
  3. Narrow the query to avoid the affected thread (e.g. exclude drafts: -in:drafts).
  4. Reload Gmail and rerun to get a fresh, consistent batch-view payload.
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function hasThreadId(record) {
  return typeof record?.[3] === 'string' && record[3].trim().length > 0;
}

Try / catch

try {
  threads = await gmailSearch(query);
} catch (error) {
  if (String(error.message).includes('thread without an id')) {
    await sleep(1500); // let in-flight sync placeholders resolve
    threads = await gmailSearch(query);
  } else throw error;
}

Prevention

When it happens

Trigger: A /i/bv thread record where position 3 is not a thread-id string (empty, wrong type, or moved to another index) — seen after Gmail payload-format changes or with placeholder rows for in-flight sync operations.

Common situations: Gmail A/B rollouts reordering record fields, drafts/dscheduled-send placeholders mid-creation, rows for threads being deleted concurrently while the search page loads.

Related errors


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