tinyhumansai/openhuman · error · anyhow::Error

SESSION_EXPIRED: backend session token expired locally — re-

Error message

SESSION_EXPIRED: backend session token expired locally — re-authentication required

What it means

Pre-flight JWT expiry check (#5503): before building a managed-backend request, `classify_session_token(profile, now)` compares the stored app-session JWT's recorded `exp` against the current clock. On `Expired`, the core publishes a local session-expiry event (`maybe_publish_local_session_expiry`) and bails with `SESSION_EXPIRED` — routing the user to re-auth instead of surfacing a misleading 'model unavailable' error. Offline/local and exp-less tokens classify as Live and are unaffected; server-side revocation is still caught by the post-call 401 net.

Source

Thrown at src/openhuman/inference/provider/openhuman_backend_model.rs:139

            self.options.auth_profile_override.as_deref(),
        )?;

        // #5503: precheck the recorded JWT `exp` BEFORE building a request, the
        // same way `require_live_session_token` guards the backend REST callers.
        // Managed inference used to fire a doomed request on an expired-but-
        // stored token and let the 401 come back — but an expired session can
        // also surface upstream as a misleading "model unavailable", which is a
        // core symptom of #5503 (all tiers "die" over a long session). Failing
        // fast as `session_expired` routes the user to re-auth instead. Offline
        // / local sessions (`is_local_session_token`) and `exp`-less tokens
        // carry no recorded expiry, so `classify_session_token` returns `Live`
        // for them — their behaviour is unchanged and the post-call 401 net
        // still covers a server-side revocation.
        match classify_session_token(profile.as_ref(), chrono::Utc::now()) {
            SessionTokenCheck::Live(token) => Ok(token),
            SessionTokenCheck::Expired => {
                maybe_publish_local_session_expiry();
                anyhow::bail!(
                    "SESSION_EXPIRED: backend session token expired locally — re-authentication required"
                )
            }
            SessionTokenCheck::Absent => {
                anyhow::bail!("No backend session: store a JWT via auth (app-session)")
            }
        }
    }

    fn base_url(&self) -> String {
        format!(
            "{}/openai/v1",
            effective_api_url(&self.api_url).trim_end_matches('/')
        )
    }

    /// Resolve the current JWT + base URL and build a fresh crate `OpenAiModel`
    /// (Bearer). Rebuilt per call because the session JWT rotates.

View on GitHub (pinned to 7491200858)

Solutions

  1. Re-authenticate (sign in again) to mint a fresh JWT, then retry.
  2. If it recurs frequently, verify the token refresh path is working (check auth logs) — tokens should refresh before expiring.
  3. Check system clock correctness on the host; a fast/skewed clock prematurely expires tokens.
  4. Restart the core after re-auth so cached auth state is rebuilt.
Defensive patterns

Strategy: try-catch

Validate before calling

use crate::openhuman::security::credentials::session_support::{classify_session_token, SessionTokenCheck};
let profile = auth.get_profile(APP_SESSION_PROVIDER, None)?;
if !matches!(classify_session_token(profile.as_ref(), chrono::Utc::now()), SessionTokenCheck::Live(_)) {
    return trigger_re_auth(); // expired or absent — don't fire the request
}

Type guard

fn session_live(profile: Option<&AuthProfile>) -> bool {
    matches!(classify_session_token(profile, chrono::Utc::now()), SessionTokenCheck::Live(_))
}

Try / catch

match model.chat(req).await {
    Err(e) if e.to_string().contains("expired locally") => prompt_re_authentication(),
    other => other,
}

Prevention

When it happens

Trigger: A managed-backend inference call when the stored JWT's `exp` is in the past — typically a desktop session running longer than the token lifetime (the #5503 'all tiers die over a long session' symptom).

Common situations: Long-lived desktop sessions whose token expired without a refresh; machine clock skew making a valid token appear expired; token refresh flow failed silently earlier.

Understand the failure class

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/de031258cc2533a0. Report an issue: GitHub.