moeru-ai/airi · error
Failed to post tweet: ${errorToMessage(error)}
Error message
Failed to post tweet: ${errorToMessage(error)} What it means
This is the top-level catch for postTweet, which composes and submits a tweet via browser automation against Twitter's web compose UI. Any failure in navigation, selector interaction, or post-URL scraping is normalized through errorToMessage and rethrown. The inner block already tolerates a missing tweet ID by falling back to a temp-<timestamp> id, so this throw means something earlier in the flow failed.
Source
Thrown at integrations/twitter-services/src/core/services/tweet.ts:263
}
// If we couldn't get the ID from the toast, check the current URL
if (!tweetId) {
const url = await page.url()
const match = url.match(/\/status\/(\d+)/)
tweetId = match?.[1] || ''
}
}
catch {
// If we fail to get the ID, generate a temporary one
tweetId = `temp-${Date.now()}`
}
return tweetId
}
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')
}
View on GitHub (pinned to 27111382b4)
Solutions
- Validate the content length against Twitter's current limit before calling postTweet.
- Confirm the compose and submit selectors still resolve on the live site.
- Check for an active session and that no rate-limit/challenge banner is present before composing.
- Log the underlying error (already done via console.error) and inspect whether it is a TimeoutError vs navigation error to target the fix.
Example fix
// before
await page.goto(TWITTER_HOME_URL)
// ... compose and post, rely on inner regex only
// after
await page.goto(TWITTER_HOME_URL)
await page.waitForSelector(SELECTORS.COMPOSE_BOX, { timeout: 10000 })
if (content.length > MAX_TWEET_LENGTH) {
throw new Error(`Content exceeds ${MAX_TWEET_LENGTH} characters`)
} Defensive patterns
Strategy: validation
Validate before calling
const MAX_LEN = 280
function validateTweetContent(content: string) {
if (!content || content.length === 0) throw new Error('Content is empty')
if (content.length > MAX_LEN) throw new Error(`Content is ${content.length} chars; max ${MAX_LEN}`)
}
// call before postTweet Try / catch
try {
return await postTweet(content, options)
}
catch (err) {
if (/limit|too many/i.test((err as Error).message)) {
// back off; do not retry immediately
throw new Error('Rate limited; retry later')
}
throw err
} Prevention
- Pre-validate content length and media against current Twitter limits.
- Confirm the compose-box selectors before posting in CI.
- Log the underlying cause so a selector vs rate-limit failure is distinguishable.
When it happens
Trigger: The compose textbox or post button selector changed so page.type/click throws; the tweet content exceeds Twitter's length limit and the post button stays disabled; a rate-limit or challenge interstitial appears after clicking post; the regex extracting the tweet id from the resulting URL does not match the new URL shape (caught, but a navigation error before that point is not).
Common situations: Twitter A/B tests the compose UI; bot hits the daily post limit; media attachment flow diverges; CI runs without a valid logged-in profile so the compose box never appears.
Related errors
- Failed to retweet: ${errorToMessage(error)}
- Failed to fetch profile for @${username}
- Tweet element not found
- Failed to extract tweet data
- Failed to get tweet details: ${errorToMessage(error)}
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/50c9b47461da75b5.
Report an issue: GitHub.