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
- Complete sign-in once so an app-session JWT is stored for APP_SESSION_PROVIDER, then retry.
- 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`).
- For AgentBox/automation deployments, confirm `agentbox_mode_enabled()` is on — that mode intentionally bypasses this gate.
- 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
- Gate first-use of custom providers behind a completed sign-in.
- Ensure the core and the auth flow use the same state dir after migrations.
- Prefer local providers for setups that will never sign in.
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
- SESSION_EXPIRED: backend session not active — sign in to use
- SESSION_EXPIRED: backend session not active — sign in to res
- No backend session: store a JWT via auth (app-session)
- Failed to send magic link (${response.status})
- Request timed out. Please try again.
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/87d00315d325fc80.
Report an issue: GitHub.