moeru-ai/airi · error

Failed to fetch profile for @${username}

Error message

Failed to fetch profile for @${username}

What it means

This is the catch-all for getUserProfile, which navigates to /<username>, waits for [data-testid="UserName"], and scrapes display name, bio, avatar, and follower counts. Any failure during that sequence is logged and rethrown as a profile-specific error that does not include the underlying cause text.

Source

Thrown at integrations/twitter-services/src/core/services/user.ts:89

        ? Number.parseInt((await followElement.textContent() || '0').replace(/\D/g, ''))
        : undefined

      const followingCount = followingElement
        ? Number.parseInt((await followingElement.textContent() || '0').replace(/\D/g, ''))
        : undefined

      return {
        username,
        displayName,
        bio: bio || undefined,
        avatarUrl: avatarUrl || undefined,
        followersCount: followerCount || undefined,
        followingCount: followingCount || undefined,
      }
    }
    catch (error) {
      logger.main.error('Error fetching user profile:', (error as Error).message)
      throw new Error(`Failed to fetch profile for @${username}`)
    }
  }

  return {
    followUser,
    getUserProfile,
  }
}

View on GitHub (pinned to 27111382b4)

Solutions

  1. Verify the username exists and is viewable by the authenticated session before scraping.
  2. Confirm [data-testid="UserName"] and the follower/following count selectors still match the live profile.
  3. After goto, detect a 404/suspended/login page and throw a more specific error.
  4. Include the underlying cause in the rethrown error so diagnosis is possible.

Example fix

// before
catch (error) {
  logger.main.error('Error fetching user profile:', (error as Error).message)
  throw new Error(`Failed to fetch profile for @${username}`)
}

// after
catch (error) {
  const cause = (error as Error).message
  logger.main.error('Error fetching user profile:', cause)
  throw new Error(`Failed to fetch profile for @${username}: ${cause}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!/^[A-Za-z0-9_]{1,15}$/.test(username)) {
  throw new Error(`Invalid Twitter username: ${username}`)
}

Try / catch

try {
  return await getUserProfile(username)
}
catch (err) {
  if (/doesn't exist|suspended|User suspended/i.test((err as Error).message)) {
    return null
  }
  throw err
}

Prevention

When it happens

Trigger: The username does not exist (404 / 'This account doesn't exist' page) so [data-testid="UserName"] never appears and waitForSelector times out; the account is suspended or protected; the session is logged out and a login wall appears; a sub-selector for bio/avatar/follower count changed and the scrape throws.

Common situations: Looking up a renamed/deleted account; running without a valid session; Twitter changes the profile DOM; follower count element moved behind a click.

Related errors


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