{"record":{"id":"19067f6b2f40d32c","repo":"jackwener/OpenCLI","slug":"gmail-search","errorCode":"gmail search","errorMessage":"No threads matched \"${normalizedQuery}\"","messagePattern":"No threads matched \"(.+?)\"","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"warning","filePath":"clis/gmail/utils.js","lineNumber":542,"sourceCode":"      'bv',\n      pageNumber === 1 ? 'thread list' : `thread pagination page ${pageNumber}`,\n    );\n    const pageRows = bodies.flatMap(parseBatchView);\n    let added = 0;\n    for (const row of pageRows) {\n      if (seen.has(row.threadId)) continue;\n      seen.add(row.threadId);\n      rows.push(row);\n      added += 1;\n      if (rows.length >= limit) return rows;\n    }\n    if (pageNumber > 1 && added === 0) {\n      throw new CommandExecutionError(`Gmail thread pagination page ${pageNumber} repeated an earlier response; refusing partial results`);\n    }\n    if (pageRows.length < PAGE_SIZE) break;\n  }\n  if (rows.length === 0) {\n    throw new EmptyResultError('gmail search', `No threads matched \"${normalizedQuery}\"`);\n  }\n  return rows.slice(0, limit);\n}\n\nexport async function listLabels(page, account = 0) {\n  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);","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/gmail/utils.js#L524-L560","documentation":"EmptyResultError with code 'gmail search' thrown when a search completed successfully but matched zero threads. The library treats 'no results' as a distinct, expected condition so callers can handle it separately from real failures.","triggerScenarios":"queryThreads(page, query) where the query matches no mail: overly narrow filters, wrong label names, mail in an account that has none, misspelled Gmail operators.","commonSituations":"Searching a secondary/test account that has no mail; using operators like label:foo where the label doesn't exist; date filters (older_than/newer_than) excluding everything.","solutions":["Broaden the query (remove filters, widen the date range)","Verify the label/address in the query exists in the target account","Catch EmptyResultError and treat it as a valid empty outcome","Check you are on the expected account index (account option)"],"exampleFix":"// before\nconst rows = await queryThreads(page, 'from:ghost@nowhere.test');\n// after\ntry {\n  const rows = await queryThreads(page, 'from:ghost@nowhere.test');\n} catch (e) {\n  if (e instanceof EmptyResultError) return [];\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try { rows = await queryThreads(page, q); }\ncatch (e) { if (e instanceof EmptyResultError && e.code === 'gmail search') return []; throw e; }","preventionTips":["Treat EmptyResultError as a normal empty outcome, not a crash","Broaden queries programmatically when zero results are acceptable to escalate","Test queries against the target account before bulk automation"],"tags":["empty-result","gmail","search"],"backgroundTag":"empty-search-results","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}