tinyhumansai/openhuman · error · anyhow::Error

SESSION_EXPIRED: backend session not active — sign in to res

Error message

SESSION_EXPIRED: backend session not active — sign in to resume LLM work

What it means

The managed OpenHuman backend model resolves its bearer token per request (`resolve_bearer`, openhuman_backend_model.rs). The fast path asks `cron::scheduler_gate::is_signed_out()`; if the session is known-dead, it bails with this `SESSION_EXPIRED` message so the caller routes to re-auth instead of firing a doomed HTTP request.

Source

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

        self.profile.streaming_tool_chunks = enabled;
        self
    }

    fn state_dir(&self) -> PathBuf {
        self.options.openhuman_dir.clone().unwrap_or_else(|| {
            directories::UserDirs::new()
                .map(|dirs| dirs.home_dir().join(".openhuman"))
                .unwrap_or_else(|| PathBuf::from(".openhuman"))
        })
    }

    fn resolve_bearer(&self) -> anyhow::Result<String> {
        use crate::openhuman::security::credentials::session_support::{
            classify_session_token, SessionTokenCheck,
        };

        if crate::openhuman::cron::scheduler_gate::is_signed_out() {
            anyhow::bail!(
                "SESSION_EXPIRED: backend session not active — sign in to resume LLM work"
            );
        }
        let auth = AuthService::new(&self.state_dir(), self.options.secrets_encrypt);
        let profile = auth.get_profile(
            APP_SESSION_PROVIDER,
            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`

View on GitHub (pinned to 7491200858)

Solutions

  1. Sign in again — the next `resolve_bearer` call re-checks the gate after re-auth.
  2. If it persists right after signing in, restart the core so the scheduler gate's signed-out flag is cleared.
  3. Check connectivity: a gate that flipped due to transient 401s (captive portal, proxy) also recovers on restart/re-auth.
  4. For headless setups, script re-auth or use a local provider to avoid the backend dependency.
Defensive patterns

Strategy: try-catch

Validate before calling

if crate::openhuman::cron::scheduler_gate::is_signed_out() {
    return Err(anyhow::anyhow!("SESSION_EXPIRED")); // preempt the managed-backend call
}

Try / catch

match model.chat(req).await {
    Err(e) if e.to_string().starts_with("SESSION_EXPIRED") => {
        maybe_publish_local_session_expiry();
        route_to_re_auth()
    }
    other => other,
}

Prevention

When it happens

Trigger: Any managed-backend chat/embeddings call while the scheduler gate has marked the session signed out (logout, server-side 401 observed earlier, token revocation).

Common situations: Desktop left running overnight after the JWT was revoked; sign-out in one surface while another workload (chat turn, background embedding) fires; token invalidated by an account event.

Related errors


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