{"record":{"id":"248a6aec4e7f2a7b","repo":"moeru-ai/airi","slug":"tweet-element-not-found","errorCode":null,"errorMessage":"Tweet element not found","messagePattern":"Tweet element not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integrations/twitter-services/src/core/services/tweet.ts","lineNumber":279,"sourceCode":"    catch (error: unknown) {\n      console.error('Error posting tweet:', error)\n      throw new Error(`Failed to post tweet: ${errorToMessage(error)}`)\n    }\n  }\n\n  /**\n   * Gets detailed information about a specific tweet\n   */\n  async function getTweetDetails(tweetId: string): Promise<TweetDetail> {\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      // Get the main tweet element\n      const tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)\n      if (!tweetElement) {\n        throw new Error('Tweet element not found')\n      }\n\n      // Use the TweetParser to extract the main tweet data\n      const mainTweet = await TweetParser.extractTweetData(page, tweetElement)\n      if (!mainTweet) {\n        throw new Error('Failed to extract tweet data')\n      }\n\n      // Check for quoted tweet\n      let quotedTweet: Tweet | undefined\n      const quotedTweetElement = await page.$('[data-testid=\"quotedTweet\"]')\n      if (quotedTweetElement) {\n        const extractedQuotedTweet = await TweetParser.extractTweetData(page, quotedTweetElement)\n        if (extractedQuotedTweet) {\n          quotedTweet = extractedQuotedTweet\n        }\n      }\n","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/integrations/twitter-services/src/core/services/tweet.ts#L261-L297","documentation":"Inside getTweetDetails, after waiting for the tweet selector to appear, page.$(SELECTORS.TIMELINE.TWEET) is called again and returns null. This means the element that satisfied waitForSelector is no longer present (or the selector matches transient loading skeletons), so the code refuses to proceed with a null handle.","triggerScenarios":"The tweet was deleted, made private, or age-restricted between navigation and extraction; SELECTORS.TIMELINE.TWEET matches a placeholder/skeleton that unmounts; the page redirected to a login or error page after the initial selector match; tweetId does not correspond to a real status.","commonSituations":"Looking up a tweet ID scraped from an old data set where the tweet no longer exists; running against an account whose session cannot view the target tweet; selector drift where TWEET matches a tombstone element.","solutions":["Validate tweetId format and confirm the tweet exists before relying on getTweetDetails.","Tighten SELECTORS.TIMELINE.TWEET so it matches only the real article rather than loading placeholders.","After goto, detect a tombstone/login redirect and surface a more specific error.","Retry once after a short delay in case the element unmounted due to client-side hydration."],"exampleFix":"// before\nconst tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)\nif (!tweetElement) {\n  throw new Error('Tweet element not found')\n}\n\n// after\nawait page.waitForSelector(SELECTORS.TIMELINE.TWEET, { timeout: 10000 })\nconst tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)\nif (!tweetElement) {\n  const tombstone = await page.$('[data-testid=\"cellInnerDiv\"] [role=\"button\"]')\n  throw new Error(tombstone ? 'Tweet unavailable (deleted/private)' : 'Tweet element not found')\n}","handlingStrategy":"try-catch","validationCode":"if (!/^\\d+$/.test(tweetId)) {\n  throw new Error(`Invalid tweet id: ${tweetId}`)\n}","typeGuard":"function isTweetElement(el: { evaluate?: Function } | null | undefined): el is { evaluate: Function } {\n  return !!el && typeof el.evaluate === 'function'\n}","tryCatchPattern":"try {\n  return await getTweetDetails(tweetId)\n}\ncatch (err) {\n  if (/not found|unavailable/i.test((err as Error).message)) {\n    return null // treat as soft miss\n  }\n  throw err\n}","preventionTips":["Validate the tweet id format before lookup.","Detect tombstone/login redirects right after goto to give a clearer error.","Tighten the tweet selector so it does not match loading skeletons."],"tags":["dom-selector","twitter","null-check","lifecycle"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}