{"record":{"id":"9f8c9c27a7c89479","repo":"moeru-ai/airi","slug":"failed-to-search-tweets-errortomessage-error","errorCode":null,"errorMessage":"Failed to search tweets: ${errorToMessage(error)}","messagePattern":"Failed to search tweets: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integrations/twitter-services/src/core/services/tweet.ts","lineNumber":102,"sourceCode":"        }\n      }\n\n      // Wait for content to load after filter change\n      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)\n\n      // Use the TweetParser to extract tweets\n      let tweets = await TweetParser.parseTimelineTweets(page)\n\n      // Limit tweets to count if specified\n      if (options.count && options.count > 0) {\n        tweets = tweets.slice(0, options.count)\n      }\n\n      return tweets\n    }\n    catch (error: unknown) {\n      console.error('Error searching tweets:', error)\n      throw new Error(`Failed to search tweets: ${errorToMessage(error)}`)\n    }\n  }\n\n  /**\n   * Likes a tweet with the given ID\n   */\n  async function likeTweet(tweetId: string): Promise<boolean> {\n    try {\n      const page = ctx.page\n      await page.goto(`${TWITTER_BASE_URL}/i/status/${tweetId}`)\n      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)\n\n      const likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)\n      if (!likeButton) {\n        throw new Error('Like button not found')\n      }\n\n      // Check if already liked","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/twitter-services/src/core/services/tweet.ts#L84-L120","documentation":"Thrown by searchTweets as a catch-all wrapper: any error thrown inside the function (page.goto failure, waitForSelector timeout, TweetParser failure, tab click errors) is caught, logged to console.error, and re-thrown as a new Error whose message prefixes the original via errorToMessage. The original cause is not preserved on .cause, only stringified into the message.","triggerScenarios":"The Puppeteer page navigates to the search URL and a step throws: network/navigation error on page.goto; SELECTORS.TIMELINE.TWEET never appears (waitForSelector timeout) due to a login wall or layout change; the filter tab selector is stale; TweetParser.parseTimelineTweets throws on unexpected DOM.","commonSituations":"Twitter/X changed its DOM so SELECTORS are stale; session/cookies expired showing a login prompt instead of results; rate limiting or temporary block shows an interstitial; the headless browser lost connectivity; the query returned zero results and the parser expected a tweet node.","solutions":["Inspect the wrapped message (errorToMessage output) — a waitForSelector timeout points to stale selectors or a login wall; a navigation error points to connectivity.","Update SELECTORS.TIMELINE.TWEET and SEARCH.* selectors to match the current X DOM.","Re-authenticate / refresh cookies so the session is logged in before searching.","Add retry with backoff for transient network/rate-limit failures."],"exampleFix":"// before\nasync function searchTweets(query, options) {\n  try { /* ... navigate + parse ... */ return tweets }\n  catch (error) {\n    console.error('Error searching tweets:', error)\n    throw new Error(`Failed to search tweets: ${errorToMessage(error)}`)\n  }\n}\n\n// after — preserve cause and add a retry\nasync function searchTweets(query, options) {\n  for (let attempt = 0; attempt < 2; attempt++) {\n    try { /* ... navigate + parse ... */ return tweets }\n    catch (error) {\n      if (attempt === 1) throw new Error(`Failed to search tweets: ${errorToMessage(error)}`, { cause: error })\n      await sleep(1000)\n    }\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isSearchTweetsError(e: unknown): boolean {\n  return e instanceof Error && /Failed to search tweets:/.test(e.message)\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return await tweetServices.tweet.searchTweets(query)\n  } catch (e) {\n    const msg = e instanceof Error ? e.message : String(e)\n    if (/waitForSelector|timeout|net::/i.test(msg) && attempt < 2) {\n      await sleep(1500 * (attempt + 1))\n      continue\n    }\n    throw e\n  }\n}","preventionTips":["Keep SELECTORS in sync with the current X DOM to avoid waitForSelector timeouts.","Maintain an authenticated session so the search page renders results, not a login wall.","Retry with backoff for transient network or rate-limit failures.","Preserve the original cause via the Error options bag for easier diagnosis."],"tags":["twitter","puppeteer","search","browser","selector-drift","catch-all"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}