moeru-ai/airi · error

Failed to search tweets: ${errorToMessage(error)}

Error message

Failed to search tweets: ${errorToMessage(error)}

What it means

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.

Source

Thrown at integrations/twitter-services/src/core/services/tweet.ts:102

        }
      }

      // Wait for content to load after filter change
      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)

      // Use the TweetParser to extract tweets
      let tweets = await TweetParser.parseTimelineTweets(page)

      // Limit tweets to count if specified
      if (options.count && options.count > 0) {
        tweets = tweets.slice(0, options.count)
      }

      return tweets
    }
    catch (error: unknown) {
      console.error('Error searching tweets:', error)
      throw new Error(`Failed to search tweets: ${errorToMessage(error)}`)
    }
  }

  /**
   * Likes a tweet with the given ID
   */
  async function likeTweet(tweetId: string): Promise<boolean> {
    try {
      const page = ctx.page
      await page.goto(`${TWITTER_BASE_URL}/i/status/${tweetId}`)
      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)

      const likeButton = await page.$(SELECTORS.TIMELINE.LIKE_BUTTON)
      if (!likeButton) {
        throw new Error('Like button not found')
      }

      // Check if already liked

View on GitHub (pinned to 27111382b4)

Solutions

  1. Inspect the wrapped message (errorToMessage output) — a waitForSelector timeout points to stale selectors or a login wall; a navigation error points to connectivity.
  2. Update SELECTORS.TIMELINE.TWEET and SEARCH.* selectors to match the current X DOM.
  3. Re-authenticate / refresh cookies so the session is logged in before searching.
  4. Add retry with backoff for transient network/rate-limit failures.

Example fix

// before
async function searchTweets(query, options) {
  try { /* ... navigate + parse ... */ return tweets }
  catch (error) {
    console.error('Error searching tweets:', error)
    throw new Error(`Failed to search tweets: ${errorToMessage(error)}`)
  }
}

// after — preserve cause and add a retry
async function searchTweets(query, options) {
  for (let attempt = 0; attempt < 2; attempt++) {
    try { /* ... navigate + parse ... */ return tweets }
    catch (error) {
      if (attempt === 1) throw new Error(`Failed to search tweets: ${errorToMessage(error)}`, { cause: error })
      await sleep(1000)
    }
  }
}
Defensive patterns

Strategy: retry

Type guard

function isSearchTweetsError(e: unknown): boolean {
  return e instanceof Error && /Failed to search tweets:/.test(e.message)
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await tweetServices.tweet.searchTweets(query)
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e)
    if (/waitForSelector|timeout|net::/i.test(msg) && attempt < 2) {
      await sleep(1500 * (attempt + 1))
      continue
    }
    throw e
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/9f8c9c27a7c89479. Report an issue: GitHub.