zeroclaw-labs/zeroclaw · error · anyhow::Error

Twitter users/me failed ({status}): {err}

Error message

Twitter users/me failed ({status}): {err}

What it means

Raised by TwitterChannel::get_authenticated_user_id when GET https://api.x.com/2/users/me with the configured Bearer token answers non-2xx; the raw response text is included. The user id feeds filtered-stream rules, and this method runs from listen() and health_check(), so a bad token fails channel startup/health rather than individual sends.

Source

Thrown at crates/zeroclaw-channels/src/twitter.rs:92

        }

        dedup.insert(tweet_id.to_string());
        false
    }

    /// Get the authenticated user's ID for filtered stream rules.
    async fn get_authenticated_user_id(&self) -> anyhow::Result<String> {
        let resp = self
            .http_client()
            .get(format!("{TWITTER_API_BASE}/users/me"))
            .bearer_auth(&self.bearer_token)
            .send()
            .await?;

        if !resp.status().is_success() {
            let status = resp.status();
            let err = resp.text().await.unwrap_or_default();
            anyhow::bail!("Twitter users/me failed ({status}): {err}");
        }

        let data: serde_json::Value = resp.json().await?;
        let user_id = data
            .get("data")
            .and_then(|d| d.get("id"))
            .and_then(|id| id.as_str())
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                    "Missing user id in Twitter response"
                );
                anyhow::Error::msg("Missing user id in Twitter response")
            })?
            .to_string();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. For 401, update the bearer token in [channels.twitter.<alias>] config with the current value from the X developer portal.
  2. For 403 scope errors, regenerate the token with users.read and tweet.read scopes attached.
  3. For 429, check the app's rate-limit dashboard and back off; listen() retries on the next cycle.
  4. Paste the response body from the message into the API error-code lookup when the status alone is ambiguous.

Example fix

# before — token from before regeneration / missing read scopes
[channels.twitter.bot]
bearer_token = "AAAA%old-token"

# after — fresh token carrying users.read + tweet.read scopes
[channels.twitter.bot]
bearer_token = "AAAA%new-token"
Defensive patterns

Strategy: retry

Validate before calling

// Fail fast on an obviously unset token before starting the channel.
fn assert_twitter_token(bearer: &str) -> anyhow::Result<()> {
    anyhow::ensure!(!bearer.trim().is_empty(), "[channels.twitter.<alias>] bearer_token missing");
    Ok(())
}

Try / catch

match twitter.listen(tx).await {
    Ok(()) => Ok(()),
    Err(err) if err.to_string().contains("Twitter users/me failed (401)") => {
        Err(err.context("invalid bearer_token — update [channels.twitter.<alias>]")) // halt, do not retry
    }
    Err(err) if err.to_string().contains("(429)") => {
        tokio::time::sleep(Duration::from_secs(900)).await; // rate window
        twitter.listen(tx).await
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: 401 for an invalid/expired bearer token, 403 when the app lacks the users.read (or tweet.read) scope, 429 when the app exceeds its API tier's rate limits, and 403 for a suspended app or revoked token.

Common situations: The token was regenerated on the X developer portal but the config still holds the old value. App-level tokens created without the read scopes needed by /users/me. Free-tier access lapsing after tier changes.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/e2f4cba0bf01415a. Report an issue: GitHub.