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
- For 401, update the bearer token in [channels.twitter.<alias>] config with the current value from the X developer portal.
- For 403 scope errors, regenerate the token with users.read and tweet.read scopes attached.
- For 429, check the app's rate-limit dashboard and back off; listen() retries on the next cycle.
- 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
- Regenerate tokens with users.read + tweet.read (+ tweet.write/dm.write when sending) in one go so scope drift cannot bite.
- Prefer a config/secrets source for the bearer token and rotate on a schedule; 401 on /users/me is the first symptom of staleness.
- Watch the X API status page and your app's rate-limit dashboard; 429 at startup is tier pressure, not a bug.
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
- Twitter DM send failed ({status}): {err}
- Twitter create tweet failed ({status}): {err}
- ElevenLabs TTS API error ({}): {}
- Google TTS API error ({}): {}
- Twilio call failed: {body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/e2f4cba0bf01415a.
Report an issue: GitHub.