zeroclaw-labs/zeroclaw · critical

LinkedIn token refresh failed ({}): {}

Error message

LinkedIn token refresh failed ({}): {}

What it means

Thrown when the OAuth refresh-token grant to LINKEDIN_OAUTH_TOKEN_URL returns non-2xx, body included. refresh_token is invoked from api_request when the access token needs renewal; failure here means the whole API call chain fails with this error. The body for 400 typically contains error=invalid_grant or invalid_client, pinpointing whether the refresh token or the client credentials are bad.

Source

Thrown at crates/zeroclaw-tools/src/linkedin_client.rs:567

            })?;

        let client = Self::client();
        let response = client
            .post(LINKEDIN_OAUTH_TOKEN_URL)
            .form(&[
                ("grant_type", "refresh_token"),
                ("refresh_token", refresh),
                ("client_id", &creds.client_id),
                ("client_secret", &creds.client_secret),
            ])
            .send()
            .await
            .context("LinkedIn token refresh request failed")?;

        let status = response.status();
        if !status.is_success() {
            let body_text = response.text().await.unwrap_or_default();
            anyhow::bail!("LinkedIn token refresh failed ({}): {}", status, body_text);
        }

        let json: serde_json::Value = response
            .json()
            .await
            .context("Failed to parse token refresh response")?;

        let new_token = json
            .get("access_token")
            .and_then(|v| v.as_str())
            .map(String::from)
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    ERROR,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"field": "access_token"})),
                    "linkedin_client: token refresh response missing access_token"

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. For invalid_grant (400), the refresh token is dead — re-run the full OAuth authorization flow to get fresh tokens.
  2. For invalid_client (400), verify client_id and client_secret match the app that issued the refresh token (and that the secret was not rotated).
  3. Refresh proactively (e.g. scheduled) so the refresh token never idles past its inactivity expiry.
  4. Persist the NEW refresh token if the response includes one, so future refreshes keep working.

Example fix

// before: refresh token revoked by user -> LinkedIn token refresh failed (400 Bad Request): {..."invalid_grant"}
let token = client.api_request(Method::GET, &url, &creds.access_token, None).await?;

// after: detect dead refresh and fall back to interactive re-auth
match client.api_request(Method::GET, &url, &creds.access_token, None).await {
    Err(e) if e.to_string().contains("token refresh failed") => {
        // trigger the OAuth authorize flow again; do not loop retrying refresh
        return Err(e).context("re-authorization required");
    }
    other => other,
}
Defensive patterns

Strategy: try-catch

Try / catch

// Never loop on refresh failures: invalid_grant is permanent
match client.refresh_token(&creds).await {
    Ok(new_access) => Ok(new_access),
    Err(e) => {
        let m = e.to_string();
        if m.contains("invalid_grant") {
            // refresh token revoked/expired: require interactive re-authorization
            Err(e).context("LinkedIn re-authorization required")
        } else if m.contains("invalid_client") {
            Err(e).context("client_id/client_secret mismatch")
        } else { Err(e) }
    }
}

Prevention

When it happens

Trigger: Refresh token expired (LinkedIn refresh tokens live ~1 year but expire sooner if unused 6 months) or revoked by the user (400 invalid_grant); wrong client_id/client_secret pair (400 invalid_client); refresh token belonging to a different app; member re-authorized the app, invalidating the stored refresh token.

Common situations: Long-lived integrations that idle past the refresh-token inactivity window; copying tokens between a dev and prod app; user revoking app access from LinkedIn settings; rotating the app secret without updating stored credentials.

Related errors


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