jackwener/OpenCLI · error · ArgumentError
thread must be a Gmail thread id from `gmail search` or a Gm
Error message
thread must be a Gmail thread id from `gmail search` or a Gmail thread URL
What it means
legacyThreadId() accepts an id like 'abc123...' (10+ hex chars), a Gmail thread URL ending in a hex id, or a '#thread-f:<decimal>' sync id which it converts to hex. Anything else raises ArgumentError. It enforces that callers pass identifiers obtained from `gmail search` or a thread URL.
Source
Thrown at clis/gmail/utils.js:566
await ensureGmailReady(page, account, 'labels');
await installGmailCapture(page, account, 'bv', 'labels');
await submitSearch(page, 'in:anywhere', 'labels');
const bodies = await waitGmailCaptures(page, 'bv', 'labels');
const labels = bodies.flatMap(parseLabels);
const fallback = labels.length === 0 ? await renderedLabels(page, account) : [];
const unique = [...new Map([...labels, ...fallback].map((row) => [row.id, row])).values()];
if (unique.length === 0) throw new EmptyResultError('gmail labels', 'Gmail returned no labels');
return unique;
}
export function legacyThreadId(value) {
const raw = cleanString(value);
const fromUrl = raw.match(/\/(?:[a-f\d]{10,})$/i)?.[0]?.slice(1);
if (fromUrl) return fromUrl.toLowerCase();
if (/^[a-f\d]{10,}$/i.test(raw)) return raw.toLowerCase();
const sync = raw.replace(/^#/, '').match(/^thread-f:(\d+)$/);
if (sync) return BigInt(sync[1]).toString(16);
throw new ArgumentError('thread must be a Gmail thread id from `gmail search` or a Gmail thread URL');
}
export async function fetchThread(page, target, account = 0) {
const legacyId = legacyThreadId(target);
await ensureGmailReady(page, account, 'thread');
await installGmailCapture(page, account, 'fd', 'thread');
const targetState = unwrapBrowserResult(await page.evaluate(`async () => {
const row = document.querySelector('[data-legacy-thread-id="${legacyId}"]');
if (!row) return { found: false };
const clickable = row.querySelector('.y6, .bog, td:nth-child(5)') || row;
const before = location.href;
clickable.click();
await new Promise((resolve) => setTimeout(resolve, 150));
const rect = clickable.getBoundingClientRect();
return { found: true, changed: location.href !== before, x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}`), 'thread row lookup');
if (targetState?.found && !targetState.changed && typeof page.nativeClick === 'function') {
await page.nativeClick(targetState.x, targetState.y);View on GitHub (pinned to 49907e53dc)
Solutions
- Pass a thread id exactly as returned by queryThreads/gmail search
- Extract the id from the Gmail thread URL path segment before calling
- If you have '#thread-f:<n>' sync ids, pass them as-is — they are supported
- Log/inspect the value you pass; ensure it matches /^[a-f\d]{10,}$/i after cleanup
Example fix
// before await fetchThread(page, message.messageId); // wrong id type // after await fetchThread(page, thread.threadId); // thread id from gmail search
Defensive patterns
Strategy: validation
Validate before calling
const isThreadRef = (v) => typeof v === 'string' && (/^[a-f\d]{10,}$/i.test(v.trim()) || /\/[a-f\d]{10,}$/i.test(v.trim()) || /^#thread-f:\d+$/.test(v.trim())); Type guard
function isGmailThreadId(v) { if (typeof v !== 'string') return false; const raw = v.trim(); return /^[a-f\d]{10,}$/i.test(raw) || /\/[a-f\d]{10,}$/i.test(raw) || /^#thread-f:\d+$/.test(raw); } Try / catch
try { await fetchThread(page, id); } catch (e) { if (e instanceof ArgumentError) { console.error('Use a thread id from `gmail search` or a thread URL'); return; } throw e; } Prevention
- Always source thread ids from queryThreads results, not message ids
- Strip URL query strings before extracting the id from a thread URL
- Keep '#thread-f:' sync ids intact — the library converts them
When it happens
Trigger: fetchThread(page, target) with a message id instead of a thread id; passing a subject line or partial id; passing a numeric id that is too short; passing a URL with extra query params after the id.
Common situations: Confusing Gmail message ids with thread ids (they differ); copying a URL fragment with trailing parameters; using ids from a different system (e.g. IMAP UIDs).
Related errors
- Gmail search query cannot be empty
- archive search sort must be one of ${SORT_OPTIONS.join(', ')
- archive search mediatype must be one of ${MEDIATYPES.join(',
- archive search limit must be a positive integer
- archive search limit must be <= 100
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/703444444d1032ce.
Report an issue: GitHub.