tinyhumansai/openhuman · error · anyhow::Error

SESSION_EXPIRED: backend session not active — sign in to use

Error message

SESSION_EXPIRED: backend session not active — sign in to use custom providers

What it means

The custom-provider session gate in the chat factory refuses to proceed because `cron::scheduler_gate::is_signed_out()` reports the backend session as dead (factory.rs:~1899 fast path). Custom (BYOK) cloud providers require an active app session; AgentBox mode explicitly bypasses this gate. The `SESSION_EXPIRED:` prefix is the standard marker the frontend routes to re-auth.

Source

Thrown at src/openhuman/inference/provider/factory.rs:1892

/// construction-time chokepoint can never diverge on what "session active"
/// means.
pub(crate) fn verify_session_active(config: &Config) -> anyhow::Result<()> {
    // AgentBox marketplace containers run headless with no desktop
    // `app-session` JWT — the deployment is operator-controlled and ships its
    // own GMI MaaS credentials via `GMI_*` env vars. The session gate exists to
    // stop an *unregistered desktop user* from routing every workload at a
    // custom provider; that threat model doesn't apply here, so bypass it.
    // Without this, every `/run` job would fail `SESSION_EXPIRED` before
    // reaching GMI (the startup path stores only `provider:gmi-maas`).
    if crate::openhuman::agent::agentbox::agentbox_mode_enabled() {
        log::debug!(
            "[chat-factory] AgentBox mode — bypassing app-session gate for custom provider"
        );
        return Ok(());
    }
    // Fast path: the scheduler gate already knows the session is dead.
    if crate::openhuman::cron::scheduler_gate::is_signed_out() {
        anyhow::bail!(
            "SESSION_EXPIRED: backend session not active — sign in to use custom providers"
        );
    }
    // Verify the app-session JWT actually exists in auth-profiles.
    let state_dir = config
        .config_path
        .parent()
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|| {
            directories::UserDirs::new()
                .map(|d| d.home_dir().join(".openhuman"))
                .unwrap_or_else(|| std::path::PathBuf::from(".openhuman"))
        });
    let auth = AuthService::new(&state_dir, config.secrets.encrypt);
    let has_session = auth
        .get_provider_bearer_token(
            crate::openhuman::security::credentials::APP_SESSION_PROVIDER,
            None,

View on GitHub (pinned to 7491200858)

Solutions

  1. Sign in again via the app's auth flow — the gate re-checks live state on the next request.
  2. If this recurs immediately after sign-in, restart the core so the scheduler gate clears its cached signed-out state.
  3. Agents running under AgentBox are exempt — verify you are not unintentionally outside that mode.
  4. If you intended fully local operation, switch the provider to a local runtime which never crosses this gate.
Defensive patterns

Strategy: try-catch

Validate before calling

if crate::openhuman::cron::scheduler_gate::is_signed_out() {
    return Err(anyhow::anyhow!("SESSION_EXPIRED")); // route UI to re-auth before the call
}

Try / catch

match build_provider(&config).await {
    Err(e) if e.to_string().starts_with("SESSION_EXPIRED") => route_to_re_auth(),
    other => other,
}

Prevention

When it happens

Trigger: Resolving a custom/BYOK cloud provider for a chat request after sign-out was recorded — token revocation, explicit logout, or the scheduler gate observing a 401. Not triggered in AgentBox mode (`agentbox_mode_enabled()` returns early).

Common situations: Long-running desktop session whose JWT was revoked server-side; user signed out in one window while a chat/cron workload fired in another; session invalidated by a password change or device management.

Related errors


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