{"record":{"id":"703444444d1032ce","repo":"jackwener/OpenCLI","slug":"thread-must-be-a-gmail-thread-id-from-gmail-searc","errorCode":null,"errorMessage":"thread must be a Gmail thread id from `gmail search` or a Gmail thread URL","messagePattern":"thread must be a Gmail thread id from `gmail search` or a Gmail thread URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/gmail/utils.js","lineNumber":566,"sourceCode":"  await ensureGmailReady(page, account, 'labels');\n  await installGmailCapture(page, account, 'bv', 'labels');\n  await submitSearch(page, 'in:anywhere', 'labels');\n  const bodies = await waitGmailCaptures(page, 'bv', 'labels');\n  const labels = bodies.flatMap(parseLabels);\n  const fallback = labels.length === 0 ? await renderedLabels(page, account) : [];\n  const unique = [...new Map([...labels, ...fallback].map((row) => [row.id, row])).values()];\n  if (unique.length === 0) throw new EmptyResultError('gmail labels', 'Gmail returned no labels');\n  return unique;\n}\n\nexport function legacyThreadId(value) {\n  const raw = cleanString(value);\n  const fromUrl = raw.match(/\\/(?:[a-f\\d]{10,})$/i)?.[0]?.slice(1);\n  if (fromUrl) return fromUrl.toLowerCase();\n  if (/^[a-f\\d]{10,}$/i.test(raw)) return raw.toLowerCase();\n  const sync = raw.replace(/^#/, '').match(/^thread-f:(\\d+)$/);\n  if (sync) return BigInt(sync[1]).toString(16);\n  throw new ArgumentError('thread must be a Gmail thread id from `gmail search` or a Gmail thread URL');\n}\n\nexport async function fetchThread(page, target, account = 0) {\n  const legacyId = legacyThreadId(target);\n  await ensureGmailReady(page, account, 'thread');\n  await installGmailCapture(page, account, 'fd', 'thread');\n  const targetState = unwrapBrowserResult(await page.evaluate(`async () => {\n    const row = document.querySelector('[data-legacy-thread-id=\"${legacyId}\"]');\n    if (!row) return { found: false };\n    const clickable = row.querySelector('.y6, .bog, td:nth-child(5)') || row;\n    const before = location.href;\n    clickable.click();\n    await new Promise((resolve) => setTimeout(resolve, 150));\n    const rect = clickable.getBoundingClientRect();\n    return { found: true, changed: location.href !== before, x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };\n  }`), 'thread row lookup');\n  if (targetState?.found && !targetState.changed && typeof page.nativeClick === 'function') {\n    await page.nativeClick(targetState.x, targetState.y);","sourceCodeStart":548,"sourceCodeEnd":584,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/gmail/utils.js#L548-L584","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nawait fetchThread(page, message.messageId); // wrong id type\n// after\nawait fetchThread(page, thread.threadId); // thread id from gmail search","handlingStrategy":"validation","validationCode":"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()));","typeGuard":"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); }","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["validation","argument-error","gmail"],"backgroundTag":"invalid-identifier-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}