moeru-ai/airi · error

Failed to extract tweet data

Error message

Failed to extract tweet data

What it means

TweetParser.extractTweetData was given a non-null tweet element but returned a falsy value, meaning it could not pull the required fields (author, text, id, timestamp) out of the DOM. This indicates the tweet element matched but its internal structure differs from what the parser expects.

Source

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

  /**
   * Gets detailed information about a specific tweet
   */
  async function getTweetDetails(tweetId: string): Promise<TweetDetail> {
    try {
      const page = ctx.page
      await page.goto(`${TWITTER_BASE_URL}/i/status/${tweetId}`)
      await page.waitForSelector(SELECTORS.TIMELINE.TWEET)

      // Get the main tweet element
      const tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)
      if (!tweetElement) {
        throw new Error('Tweet element not found')
      }

      // Use the TweetParser to extract the main tweet data
      const mainTweet = await TweetParser.extractTweetData(page, tweetElement)
      if (!mainTweet) {
        throw new Error('Failed to extract tweet data')
      }

      // Check for quoted tweet
      let quotedTweet: Tweet | undefined
      const quotedTweetElement = await page.$('[data-testid="quotedTweet"]')
      if (quotedTweetElement) {
        const extractedQuotedTweet = await TweetParser.extractTweetData(page, quotedTweetElement)
        if (extractedQuotedTweet) {
          quotedTweet = extractedQuotedTweet
        }
      }

      // Get replies by scrolling to load more using reusable scroll logic
      const replySelector = '[data-testid="tweet"][aria-labelledby*="reply"]'

      // Try to load at least 10 replies (if available)
      await scrollToLoadMoreTweets(page, 10, replySelector)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Update TweetParser's sub-selectors against the current live tweet DOM.
  2. Ensure the element passed in is a full tweet article, not a quoted/partial fragment.
  3. Have the parser log which required field failed so the failing selector is identifiable.
  4. Pin the automation to a stable Twitter surface (e.g. the /i/status/ page) where the layout is most consistent.

Example fix

// before
const mainTweet = await TweetParser.extractTweetData(page, tweetElement)
if (!mainTweet) {
  throw new Error('Failed to extract tweet data')
}

// after
const mainTweet = await TweetParser.extractTweetData(page, tweetElement)
if (!mainTweet) {
  const html = await page.evaluate(el => el.outerHTML, tweetElement).catch(() => '<unreadable>')
  throw new Error(`Failed to extract tweet data; element snapshot: ${html.slice(0, 500)}`)
}
Defensive patterns

Strategy: validation

Validate before calling

// sanity-check the element is a full tweet article, not a quoted fragment
const isArticle = await page.evaluate(
  el => !!el?.querySelector('[data-testid="tweetText"]') && !!el?.querySelector('[role="link"]'),
  tweetElement,
)
if (!isArticle) throw new Error('Element is not a full tweet article')

Type guard

function hasTweetData(t: unknown): t is { id: string; text: string; author: unknown } {
  return !!t && typeof (t as any).id === 'string' && typeof (t as any).text === 'string'
}

Try / catch

const data = await TweetParser.extractTweetData(page, tweetElement)
if (!data) {
  // log DOM snapshot for diagnosis, then degrade
  return null
}

Prevention

When it happens

Trigger: Twitter changed the internal layout of a tweet (avatar/username/text spans restructured); the matched element is a retweet header or quoted tweet container rather than a full tweet; a required sub-selector returned nothing so the parser bailed.

Common situations: Parser written against an older DOM layout; extracted element is a quoted tweet partial; new UI variant (e.g. condensed timeline) served to the bot.

Related errors


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