moeru-ai/airi · error

Failed to retweet: ${errorToMessage(error)}

Error message

Failed to retweet: ${errorToMessage(error)}

What it means

This error wraps any failure that occurs during the retweet flow in the Twitter services module, which drives Twitter's web UI through a headless browser (Puppeteer-style page automation). The retweet sequence clicks the confirm button and then polls the timeline retweet button's aria-pressed attribute to verify success within a 5 second window. errorToMessage normalizes the underlying cause (timeout, navigation error, closed page, selector mismatch) into the thrown string.

Source

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

      }

      await retweetButton.click()

      // Wait for retweet confirmation dialog and click it
      await page.waitForSelector('[data-testid="retweetConfirm"]')
      await page.click('[data-testid="retweetConfirm"]')

      // Wait for the retweet to register
      await page.waitForFunction(
        `document.querySelector('${SELECTORS.TIMELINE.RETWEET_BUTTON}')?.getAttribute('aria-pressed') === 'true'`,
        { timeout: 5000 },
      )

      return true
    }
    catch (error: unknown) {
      console.error('Error retweeting:', error)
      throw new Error(`Failed to retweet: ${errorToMessage(error)}`)
    }
  }

  /**
   * Posts a new tweet with the given content and options
   */
  async function postTweet(content: string, options: PostOptions = {}): Promise<string> {
    try {
      const page = ctx.page
      // Go to home page where you can compose a tweet
      await page.goto(TWITTER_HOME_URL)

      // Wait for the tweet composer to load
      await page.waitForSelector(SELECTORS.COMPOSE.TWEET_INPUT)

      // Type the tweet content
      await page.click(SELECTORS.COMPOSE.TWEET_INPUT)
      await page.type(SELECTORS.COMPOSE.TWEET_INPUT, content)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Re-verify the session is authenticated (navigate to home, check for the compose box) before calling retweet.
  2. Confirm SELECTORS.TIMELINE.RETWEET_BUTTON and data-testid="retweetConfirm" still match the live DOM; update them if Twitter changed the UI.
  3. Raise the waitForFunction timeout above 5000ms or retry once on timeout, since retweets sometimes register slowly.
  4. Guard ctx.page for isClosed() before entry and re-create the page if it was torn down.

Example fix

// before
await page.waitForFunction(
  `document.querySelector('${SELECTORS.TIMELINE.RETWEET_BUTTON}')?.getAttribute('aria-pressed') === 'true'`,
  { timeout: 5000 },
)

// after
await page.waitForFunction(
  `document.querySelector('${SELECTORS.TIMELINE.RETWEET_BUTTON}')?.getAttribute('aria-pressed') === 'true'`,
  { timeout: 15000 },
).catch(async () => {
  // re-check via the API-free heuristic: reload the tweet and inspect retweeted state
  await page.reload({ waitUntil: 'networkidle' })
})
Defensive patterns

Strategy: try-catch

Validate before calling

if (!ctx.page || ctx.page.isClosed?.()) {
  throw new Error('Page is not available; recreate it before retweeting')
}
// optional session pre-check
await ctx.page.goto(TWITTER_HOME_URL)
const loggedIn = await ctx.page.$('[data-testid="SideNav_NewTweet_Button"]')
if (!loggedIn) throw new Error('Session expired; re-authenticate before retweeting')

Type guard

function isRetweetable(page: { click: (s: string) => Promise<unknown> } | null | undefined): page is { click: (s: string) => Promise<unknown> } {
  return !!page && typeof page.click === 'function'
}

Try / catch

try {
  await retweet(tweetUrl)
}
catch (err) {
  const msg = (err as Error).message
  if (/timeout|aria-pressed/i.test(msg)) {
    // transient: wait and retry once
    await new Promise(r => setTimeout(r, 2000))
    return retweet(tweetUrl)
  }
  if (/login|session/i.test(msg)) throw new Error('Re-auth required')
  throw err
}

Prevention

When it happens

Trigger: Calling retweet(tweetUrl) when the session cookie is expired so Twitter shows a login wall instead of the retweet confirm; the data-testid="retweetConfirm" attribute was renamed by Twitter so page.click rejects; the waitForFunction exceeds its 5000ms timeout because aria-pressed never flips to 'true' (slow network, rate-limit interstitial, or already-retweeted state); ctx.page was closed or navigated away mid-flow.

Common situations: Twitter ships a DOM/A-B test that renames data-testid values; long-running bot sessions lose their auth cookie; running against a flaky proxy that stalls page loads; calling retweet on a tweet you already retweeted so the confirm path differs.

Related errors


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