{"record":{"id":"e2f4cba0bf01415a","repo":"zeroclaw-labs/zeroclaw","slug":"twitter-users-me-failed-status-err","errorCode":null,"errorMessage":"Twitter users/me failed ({status}): {err}","messagePattern":"Twitter users/me failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/twitter.rs","lineNumber":92,"sourceCode":"        }\n\n        dedup.insert(tweet_id.to_string());\n        false\n    }\n\n    /// Get the authenticated user's ID for filtered stream rules.\n    async fn get_authenticated_user_id(&self) -> anyhow::Result<String> {\n        let resp = self\n            .http_client()\n            .get(format!(\"{TWITTER_API_BASE}/users/me\"))\n            .bearer_auth(&self.bearer_token)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let err = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"Twitter users/me failed ({status}): {err}\");\n        }\n\n        let data: serde_json::Value = resp.json().await?;\n        let user_id = data\n            .get(\"data\")\n            .and_then(|d| d.get(\"id\"))\n            .and_then(|id| id.as_str())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n                    \"Missing user id in Twitter response\"\n                );\n                anyhow::Error::msg(\"Missing user id in Twitter response\")\n            })?\n            .to_string();\n","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/twitter.rs#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before — token from before regeneration / missing read scopes\n[channels.twitter.bot]\nbearer_token = \"AAAA%old-token\"\n\n# after — fresh token carrying users.read + tweet.read scopes\n[channels.twitter.bot]\nbearer_token = \"AAAA%new-token\"","handlingStrategy":"retry","validationCode":"// Fail fast on an obviously unset token before starting the channel.\nfn assert_twitter_token(bearer: &str) -> anyhow::Result<()> {\n    anyhow::ensure!(!bearer.trim().is_empty(), \"[channels.twitter.<alias>] bearer_token missing\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match twitter.listen(tx).await {\n    Ok(()) => Ok(()),\n    Err(err) if err.to_string().contains(\"Twitter users/me failed (401)\") => {\n        Err(err.context(\"invalid bearer_token — update [channels.twitter.<alias>]\")) // halt, do not retry\n    }\n    Err(err) if err.to_string().contains(\"(429)\") => {\n        tokio::time::sleep(Duration::from_secs(900)).await; // rate window\n        twitter.listen(tx).await\n    }\n    Err(err) => Err(err),\n}","preventionTips":["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."],"tags":["twitter","x-api","http","auth","oauth"],"backgroundTag":"twitter-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}