moeru-ai/airi · error
Tweet element not found
Error message
Tweet element not found
What it means
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.
Source
Thrown at integrations/twitter-services/src/core/services/tweet.ts:279
catch (error: unknown) {
console.error('Error posting tweet:', error)
throw new Error(`Failed to post tweet: ${errorToMessage(error)}`)
}
}
/**
* 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
}
}
View on GitHub (pinned to 27111382b4)
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.
Example fix
// before
const tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)
if (!tweetElement) {
throw new Error('Tweet element not found')
}
// after
await page.waitForSelector(SELECTORS.TIMELINE.TWEET, { timeout: 10000 })
const tweetElement = await page.$(SELECTORS.TIMELINE.TWEET)
if (!tweetElement) {
const tombstone = await page.$('[data-testid="cellInnerDiv"] [role="button"]')
throw new Error(tombstone ? 'Tweet unavailable (deleted/private)' : 'Tweet element not found')
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!/^\d+$/.test(tweetId)) {
throw new Error(`Invalid tweet id: ${tweetId}`)
} Type guard
function isTweetElement(el: { evaluate?: Function } | null | undefined): el is { evaluate: Function } {
return !!el && typeof el.evaluate === 'function'
} Try / catch
try {
return await getTweetDetails(tweetId)
}
catch (err) {
if (/not found|unavailable/i.test((err as Error).message)) {
return null // treat as soft miss
}
throw err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to retweet: ${errorToMessage(error)}
- Failed to post tweet: ${errorToMessage(error)}
- Failed to extract tweet data
- Failed to fetch profile for @${username}
- Godot stage is not running.
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/248a6aec4e7f2a7b.
Report an issue: GitHub.