{"record":{"id":"0c14d6a5a7a9ce9c","repo":"tinyhumansai/openhuman","slug":"session-expired-backend-session-not-active-sign","errorCode":null,"errorMessage":"SESSION_EXPIRED: backend session not active — sign in to use custom providers","messagePattern":"SESSION_EXPIRED: backend session not active — sign in to use custom providers","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/openhuman/inference/provider/factory.rs","lineNumber":1892,"sourceCode":"/// construction-time chokepoint can never diverge on what \"session active\"\n/// means.\npub(crate) fn verify_session_active(config: &Config) -> anyhow::Result<()> {\n    // AgentBox marketplace containers run headless with no desktop\n    // `app-session` JWT — the deployment is operator-controlled and ships its\n    // own GMI MaaS credentials via `GMI_*` env vars. The session gate exists to\n    // stop an *unregistered desktop user* from routing every workload at a\n    // custom provider; that threat model doesn't apply here, so bypass it.\n    // Without this, every `/run` job would fail `SESSION_EXPIRED` before\n    // reaching GMI (the startup path stores only `provider:gmi-maas`).\n    if crate::openhuman::agent::agentbox::agentbox_mode_enabled() {\n        log::debug!(\n            \"[chat-factory] AgentBox mode — bypassing app-session gate for custom provider\"\n        );\n        return Ok(());\n    }\n    // Fast path: the scheduler gate already knows the session is dead.\n    if crate::openhuman::cron::scheduler_gate::is_signed_out() {\n        anyhow::bail!(\n            \"SESSION_EXPIRED: backend session not active — sign in to use custom providers\"\n        );\n    }\n    // Verify the app-session JWT actually exists in auth-profiles.\n    let state_dir = config\n        .config_path\n        .parent()\n        .map(std::path::PathBuf::from)\n        .unwrap_or_else(|| {\n            directories::UserDirs::new()\n                .map(|d| d.home_dir().join(\".openhuman\"))\n                .unwrap_or_else(|| std::path::PathBuf::from(\".openhuman\"))\n        });\n    let auth = AuthService::new(&state_dir, config.secrets.encrypt);\n    let has_session = auth\n        .get_provider_bearer_token(\n            crate::openhuman::security::credentials::APP_SESSION_PROVIDER,\n            None,","sourceCodeStart":1874,"sourceCodeEnd":1910,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/inference/provider/factory.rs#L1874-L1910","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Sign in again via the app's auth flow — the gate re-checks live state on the next request.","If this recurs immediately after sign-in, restart the core so the scheduler gate clears its cached signed-out state.","Agents running under AgentBox are exempt — verify you are not unintentionally outside that mode.","If you intended fully local operation, switch the provider to a local runtime which never crosses this gate."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if crate::openhuman::cron::scheduler_gate::is_signed_out() {\n    return Err(anyhow::anyhow!(\"SESSION_EXPIRED\")); // route UI to re-auth before the call\n}","typeGuard":null,"tryCatchPattern":"match build_provider(&config).await {\n    Err(e) if e.to_string().starts_with(\"SESSION_EXPIRED\") => route_to_re_auth(),\n    other => other,\n}","preventionTips":["Handle sign-out events by pausing cloud-role workloads until re-auth completes.","Remember AgentBox mode intentionally bypasses this gate — verify your mode matches intent.","Treat any `SESSION_EXPIRED:`-prefixed error as an auth-routing signal, not a provider bug."],"tags":["session-expired","auth","custom-providers","factory"],"backgroundTag":"session-expired","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}