tinyhumansai/openhuman · error · anyhow::Error

SESSION_EXPIRED: no backend session — sign in to use OpenHum

Error message

SESSION_EXPIRED: no backend session — sign in to use OpenHuman

What it means

The custom-provider session gate passed the fast path (not marked signed out) but found no usable app-session JWT: `AuthService::get_provider_bearer_token(APP_SESSION_PROVIDER)` returned nothing or blank (factory.rs:~1908-1915). The credential store simply has no stored backend session token, so the `SESSION_EXPIRED` bail fires.

Source

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

    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,
        )?
        .filter(|s| !s.trim().is_empty())
        .is_some();
    if !has_session {
        anyhow::bail!("SESSION_EXPIRED: no backend session — sign in to use OpenHuman")
    }
    Ok(())
}

fn resolve_primary_cloud_provider_string(config: &Config) -> String {
    let primary = config
        .primary_cloud
        .as_deref()
        .and_then(|id| config.cloud_providers.iter().find(|entry| entry.id == id));

    if primary.is_some_and(is_openhuman_cloud_entry) {
        if let Some(legacy) = legacy_custom_inference_provider_string(config) {
            return legacy;
        }
        // Primary is explicitly OpenHuman but inference_url points at a custom
        // endpoint with no matching provider entry — this is a half-migrated BYOK
        // config. Fail closed so the user sees an actionable error rather than
        // silently routing through the managed backend.

View on GitHub (pinned to 7491200858)

Solutions

  1. Complete sign-in once so an app-session JWT is stored for APP_SESSION_PROVIDER, then retry.
  2. If you believe you are signed in, verify the core's state dir matches the one auth wrote to (check `config.config_path` parent vs `~/.openhuman`).
  3. For AgentBox/automation deployments, confirm `agentbox_mode_enabled()` is on — that mode intentionally bypasses this gate.
  4. Re-authenticate after a workspace reset or profile migration.
Defensive patterns

Strategy: validation

Validate before calling

let has_session = AuthService::new(&state_dir, config.secrets.encrypt)
    .get_provider_bearer_token(APP_SESSION_PROVIDER, None)?
    .map(|t| !t.trim().is_empty())
    .unwrap_or(false);
if !has_session { return prompt_sign_in(); }

Try / catch

match gate(&config) {
    Err(e) if e.to_string().contains("no backend session") => prompt_sign_in(),
    other => other,
}

Prevention

When it happens

Trigger: First use of a custom provider before ever signing in; the auth-profiles store lost the app-session entry (corrupted store, workspace reset, fresh `~/.openhuman` state dir); sign-in recorded in a different user/workspace than the one the core reads.

Common situations: Fresh installs where the user skipped sign-in and configured a BYOK key directly; workspace switched (state_dir resolves elsewhere); migrated machines where the credentials store did not come along.

Related errors


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