fix(jsonrpc): narrow SessionExpired to backend-boundary signal (#2286)#2302
fix(jsonrpc): narrow SessionExpired to backend-boundary signal (#2286)#2302obchain wants to merge 2 commits into
Conversation
OpenHuman backend auth boundary calls now bail with
"SESSION_EXPIRED: {method} {path} failed (401): {body}". The
downstream dispatch-site classifier in core::jsonrpc keys off the
sentinel so only true OpenHuman session-expiry signals clear the
local token — BYO-key provider, integration, and channel-status
401s no longer route through SessionExpired.
Skip Sentry on 401 (the SessionExpired subscriber owns the teardown
and a signed-out user is an expected state).
Refs tinyhumansai#2286.
…nal (tinyhumansai#2286) is_session_expired_error used to match any error containing "401 + unauthorized" or "invalid token", which signed users out for downstream / integration / BYO-key 401s that had nothing to do with the OpenHuman session (Discord card-open, BYO-key mis-config, Lark channel 401, MCP server 401, …). Each false positive published DomainEvent::SessionExpired, the credentials subscriber cleared the local token, and the user bounced back to Welcome. The classifier now matches only: - The SESSION_EXPIRED sentinel emitted at the backend boundary (api/rest.rs 401 path and openhuman_backend.rs no-session path). - The two literal local guards "no backend session token" and "session jwt required". Downstream / integration / BYO-key 401s surface as typed recoverable errors via their own provider paths. Also annotate the SessionExpired publish log with a match marker (backend-sentinel / no-backend-session-token / session-jwt-required) so future debugging can tell which boundary fired without re-scanning the error string. Closes tinyhumansai#2286.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR narrows ChangesSession-expiration narrowing
Sequence DiagramsequenceDiagram
participant User as User / RPC Client
participant JSONRPCHandler as invoke_method
participant BackendClient as authed_json
participant BackendAPI as Backend API
User->>JSONRPCHandler: call RPC method
JSONRPCHandler->>BackendClient: invoke authed_json()
BackendClient->>BackendAPI: HTTP request
alt True OpenHuman session expired
BackendAPI-->>BackendClient: 401 Unauthorized
BackendClient->>BackendClient: log session_expired event
BackendClient-->>JSONRPCHandler: error with SESSION_EXPIRED: prefix
JSONRPCHandler->>JSONRPCHandler: is_session_expired_error matches sentinel
JSONRPCHandler->>JSONRPCHandler: log method + matched=backend_sentinel
JSONRPCHandler->>User: publish SessionExpired event
else Downstream provider 401
BackendAPI-->>BackendClient: 401 Unauthorized (provider auth)
BackendClient-->>JSONRPCHandler: error without SESSION_EXPIRED
JSONRPCHandler->>JSONRPCHandler: is_session_expired_error returns false
JSONRPCHandler-->>User: return recoverable provider error
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
src/api/rest.rswith aSESSION_EXPIRED:sentinel prefix.is_session_expired_errorinsrc/core/jsonrpc.rsto match only the sentinel and the two literal local guards ("no backend session token","session jwt required")."401 + unauthorized"/"invalid token"match that signed users out on downstream / integration / BYO-key 401s (Discord card-open, BYO-key mis-config, Lark channel 401, MCP server 401, …).SessionExpiredpublish log line with a match marker so future debugging tells which boundary fired.jsonrpc_tests.rs(BYO-key, integration, channel, sentinel, generic-401 negative) andrest_tests.rs(401 sentinel emission + non-401 negative).Problem
is_session_expired_error(src/core/jsonrpc.rs:230) used.contains("401") && .contains("unauthorized")plus.contains("invalid token"). Any RPC path that bubbled a 401 from a downstream provider or integration matched and publishedDomainEvent::SessionExpired; the credentials subscriber then cleared the local OpenHuman token and bounced the user back to Welcome / Google sign-in. A single mis-configured BYO API key, a Discord card-open status fetch, or an MCP server token refresh would nuke the entire app session. asura.zhou (May 16) and Claudy (Discord card click) both hit this cascade.Solution
Two-step:
src/api/rest.rsis the single place every legitimate OpenHuman backend call routes through (effective_backend_api_url→authed_json). When the response status is 401, bail with"SESSION_EXPIRED: {method} {path} failed (401): {body}"and skip the Sentry report (theSessionExpiredsubscriber owns the teardown — a signed-out user is an expected state). Other 401-emitting sites (inference/provider/ops.rs,mcp_client/client.rs,channels/providers/lark.rs,http_host/auth.rs) keep their existing error shapes; they no longer match the classifier.is_session_expired_errornow matches only:SESSION_EXPIRED— authoritative boundary marker (also already emitted byopenhuman/inference/provider/openhuman_backend.rs:50for the no-session inference path)."no backend session token"— local guard for the missing-profile case."session jwt required"— local guard for the no-JWT-in-store case.Also added a
matched=<marker>field to theSessionExpiredpublish log so future debugging can tell at a glance whether the signal came from the backend sentinel or one of the local guards.Submission Checklist
## Related— N/A: no matrix row affected.Closes #NNNin the## Relatedsection.Impact
.contains()chain).SESSION_EXPIREDsentinel in error strings is internal to the dispatch classifier and not part of any public API.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/2286-narrow-session-expiredValidation Run
pnpm --filter openhuman-app format:check— N/A: no frontend changes.pnpm typecheck— N/A: no TypeScript changes.cargo test --lib is_session_expired(9/9 pass);cargo test --lib authed_json(4/4 pass);cargo test --lib -- jsonrpc rest(113/114, 1 ignored — pre-existing).cargo fmt --all --checkclean;cargo checkclean.Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
SessionExpiredand clears the local token (via the new sentinel path). Theopenhuman_backend.rsno-session inference path continues to match (already used the sentinel pre-SessionExpired clears the session for unrelated backend 401s #2286).Duplicate / Superseded PR Handling
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests