zeroclaw-labs/zeroclaw · critical · anyhow::Error
token refresh failed ({status}): {body}
Error message
token refresh failed ({status}): {body} What it means
Raised by RedditChannel::refresh_access_token when POST https://www.reddit.com/api/v1/access_token (grant_type=refresh_token, HTTP Basic client_id:client_secret) returns a non-2xx status. The response body is included verbatim — Reddit returns JSON like {'error': 'invalid_grant'} for revoked/invalid refresh tokens or 401 invalid_client for a bad app credential pair. Every Reddit operation funnels through this: get_access_token, listen polling, fetch_inbox, mark_read and send all fail when it breaks.
Source
Thrown at crates/zeroclaw-channels/src/reddit.rs:118
let client = self.http_client();
let resp = client
.post(REDDIT_TOKEN_URL)
.basic_auth(&self.client_id, Some(&self.client_secret))
.header("User-Agent", USER_AGENT)
.form(&[
("grant_type", "refresh_token"),
("refresh_token", &self.refresh_token),
])
.send()
.await?;
let status = resp.status();
if !status.is_success() {
let body = resp
.text()
.await
.unwrap_or_else(|e| format!("<failed to read response: {e}>"));
bail!("token refresh failed ({status}): {body}");
}
let token_resp: RedditTokenResponse = resp.json().await?;
let mut auth = self.auth.lock();
auth.access_token = token_resp.access_token;
auth.expires_at =
Instant::now() + Duration::from_secs(token_resp.expires_in.saturating_sub(60));
Ok(())
}
/// Get a valid access token, refreshing if expired.
async fn get_access_token(&self) -> Result<String> {
{
let auth = self.auth.lock();
if !auth.access_token.is_empty() && Instant::now() < auth.expires_at {
return Ok(auth.access_token.clone());
}
}View on GitHub (pinned to 88bb9c8533)
Solutions
- If the body says invalid_grant or status is 400: the refresh token is dead — re-run the OAuth flow for the Reddit app and replace the stored refresh_token
- If 401/invalid_client: the client_id/client_secret pair does not match the app that issued the refresh token — fix the config
- If 429: back off and lower polling rate (Reddit enforces 60 requests/minute; POLL_INTERVAL is 5s)
- Verify the configured User-Agent — Reddit rejects generic/default agents
- Check for a second process using the same credentials doubling the request rate
Defensive patterns
Strategy: try-catch
Try / catch
match channel.get_access_token().await {
Err(err) => {
let msg = format!("{err:#}");
if msg.starts_with("token refresh failed (400") || msg.starts_with("token refresh failed (401") {
return Err(anyhow!("Reddit credentials need manual re-auth: {msg}")).context("stop retrying"); // permanent
}
tokio::time::sleep(Duration::from_secs(30)).await; // 429/5xx: transient
channel.get_access_token().await
}
ok => ok,
} Prevention
- Never retry 400/401 refresh failures — invalid_grant means the refresh token is revoked and only re-consent fixes it
- Keep client_id/client_secret/refresh_token from the same Reddit app in config
- Stay under Reddit's 60 req/min budget; the token endpoint rate-limits too
- Alert on refresh failures distinctly from API failures — they indicate credential rot, not load
When it happens
Trigger: Any Reddit API call with an empty/expired cached access token triggers refresh_access_token; a 400 invalid_grant (refresh token revoked or mistyped), 401 (wrong client_id/client_secret for the app that issued the token), 429 (IP or app rate limited), or 5xx triggers the bail before token_resp parsing.
Common situations: User revoked app access in Reddit settings, invalidating the stored refresh token; refresh_token issued by one Reddit app but client_id/secret from another; placeholder or truncated secrets in config; exceeding Reddit's 60 req/min budget so token endpoint also rate limits; datacenter IP blocked by Reddit.
Related errors
- OAuth refresh failed (HTTP {status}): {detail}
- MiniMax OAuth refresh failed (HTTP {status}): {detail}
- comment reply failed ({status}): {body}
- DM failed ({status}): {body}
- MiniMax OAuth refresh failed: {detail}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/36cb16fb051a5a0e.
Report an issue: GitHub.