From ac3cfd8be4e0573a31eb4538a4cd1059d78babf2 Mon Sep 17 00:00:00 2001 From: Ghost Scripter Date: Thu, 28 May 2026 01:45:07 +0530 Subject: [PATCH 1/3] fix(observability): classify OpenHuman backend 'Invalid token' 401 as SessionExpired MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenHuman backend rejects an expired/revoked JWT with the envelope `{"success":false,"error":"Invalid token"}` (vs. the explicit `"Session expired. Please log in again."` body that the existing classifier already catches). Same emit site (`providers::ops::api_error` → `web_channel.run_chat_task`), same wrapping, same expected user state — just a different body substring chosen by the backend's JWT-validity branch. Issue #2286 deliberately stopped matching bare `"Invalid token"` as session-expired because that string also surfaces from Discord / OAuth provider rejections, which are actionable scoped errors that must reach Sentry. We preserve that contract with a conjunctive matcher: BOTH the OpenHuman-scoped `"OpenHuman API error (401"` prefix AND the envelope-shaped `"\"error\":\"Invalid token\""` must be present. #2286 cases still route to Sentry (verified by the existing `does_not_classify_byo_key_provider_401_as_session_expired` test staying green): - `"Invalid token"` → None ✓ - `"got an invalid token here"` → None ✓ - `"OpenAI API error (401 Unauthorized): invalid_api_key"` → None ✓ - `"Anthropic API error (401 Unauthorized): ..."` → None ✓ Targets Sentry OPENHUMAN-TAURI-4P0 (issue 5332): low volume so far (1 event) but the wire shape is durable — every OpenHuman user with a stale JWT will hit this on the next agent turn, so quietly demoting it to a `warn!` log keeps the noise from compounding. --- src/core/observability.rs | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/core/observability.rs b/src/core/observability.rs index f1a58c543..9aeb4db30 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -230,6 +230,15 @@ pub fn expected_error_kind(message: &str) -> Option { /// `channels::providers::web::run_chat_task` (OPENHUMAN-TAURI-26). The /// `"session expired"` substring anchors the match to the OpenHuman /// backend's session-renewal body, not the bare numeric status. +/// - `"OpenHuman API error (401 Unauthorized): {…\"error\":\"Invalid token\"…}"` +/// — same emit site, same wire shape as the `Session expired` body, but the +/// OpenHuman backend swaps in `"Invalid token"` for the JWT-validity +/// rejection branch (vs. the explicit session-renewal branch). +/// OPENHUMAN-TAURI-4P0. The conjunctive anchor — `"OpenHuman API error +/// (401"` **and** the envelope-shaped `"\"error\":\"Invalid token\""` — +/// keeps the #2286 contract intact: bare `"Invalid token"`, OpenAI / +/// Anthropic BYO-key 401s, Discord upstream-bot-token rejections, and +/// provider scope errors still route to Sentry as actionable. /// - `"SESSION_EXPIRED: backend session not active — sign in to resume LLM work"` /// — the `scheduler_gate::is_signed_out` sentinel from /// `providers::openhuman_backend::resolve_bearer`. @@ -247,6 +256,16 @@ pub fn is_session_expired_message(msg: &str) -> bool { || lower.contains("no backend session token") || lower.contains("session jwt required") || msg.contains("SESSION_EXPIRED") + // OPENHUMAN-TAURI-4P0 — OpenHuman backend's "Invalid token" 401 + // envelope. Both anchors must be present: the OpenHuman-scoped + // `"OpenHuman API error (401"` prefix (so a third-party provider's + // `"OpenAI API error (401 Unauthorized): invalid_api_key"` cannot + // match), AND the envelope-shaped `"\"error\":\"Invalid token\""` + // (so bare prose mentions of "invalid token" — Discord OAuth + // failures, generic upstream errors covered by #2286 — stay + // actionable in Sentry). + || (msg.contains("OpenHuman API error (401") + && msg.contains("\"error\":\"Invalid token\"")) } /// Detect the in-process-core boot-window shape: a sibling component @@ -2454,6 +2473,40 @@ mod tests { } } + /// OPENHUMAN-TAURI-4P0: the OpenHuman backend rejects an expired/ + /// revoked JWT with the envelope `{"success":false,"error":"Invalid + /// token"}` (vs. the explicit `"Session expired. Please log in again."` + /// body covered by `classifies_session_expired_messages`). Same emit + /// site, same wrapping by `web_channel.run_chat_task`, but the body + /// substring is different. + /// + /// The matcher uses a conjunctive `"OpenHuman API error (401"` + + /// envelope-shaped `"\"error\":\"Invalid token\""` anchor pair so the + /// #2286 contract for bare `"Invalid token"` / BYO-key 401s is + /// preserved — `does_not_classify_byo_key_provider_401_as_session_expired` + /// pins that and must stay green. + #[test] + fn classifies_openhuman_invalid_token_401_as_session_expired() { + // Verbatim wire shape from the OPENHUMAN-TAURI-4P0 event payload. + let msg = r#"run_chat_task failed client_id=lssXhQidBfzGXG9k thread_id=thread-743193ba-f0c1-4008-b665-64d3030d1453 request_id=00696b71-fa05-4574-bcdb-5744a5dac6ea error=OpenHuman API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#; + assert_eq!( + expected_error_kind(msg), + Some(ExpectedErrorKind::SessionExpired), + "OPENHUMAN-TAURI-4P0 verbatim wire shape must classify as SessionExpired" + ); + + // Unwrapped emit shape (without the run_chat_task prefix) — also + // appears at provider/agent layers; the substring matcher must + // catch it regardless of caller wrapping. + assert_eq!( + expected_error_kind( + r#"OpenHuman API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"# + ), + Some(ExpectedErrorKind::SessionExpired), + "unwrapped OpenHuman invalid-token envelope must classify as SessionExpired" + ); + } + #[test] fn does_not_classify_byo_key_provider_401_as_session_expired() { // Critical: a BYO-key 401 from OpenAI / Anthropic etc. is an From 2a1706b01266b454fffaec4a8c1e2e1dda65d587 Mon Sep 17 00:00:00 2001 From: Ghost Scripter Date: Thu, 28 May 2026 02:52:55 +0530 Subject: [PATCH 2/3] fix(observability): classify Embedding API 'Invalid token' 401 as SessionExpired (TAURI-RUST-4K5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TAURI-RUST-4K5 (~118 events, escalating on 0.56.0, domain=embeddings operation=openai_embed status=401) carries the same OpenHuman backend `{"success":false,"error":"Invalid token"}` envelope as 4P0, but the embedding client at `src/openhuman/embeddings/openai.rs:139` wraps it with the `"Embedding API error"` prefix instead of `"OpenHuman API error"`. The breadcrumb `[scheduler_gate] signed_out false -> true` immediately preceding the 401 in the event payload confirms it's the same session-expired cause, just emitted at the embedding layer. The conjunctive `"OpenHuman API error (401"` anchor added in the previous commit catches the chat-API path; this commit adds a parallel `"Embedding API error (401"` anchor so the embedding path also routes to SessionExpired. The envelope-shaped `"\"error\":\"Invalid token\""` gate stays the same, so third-party BYO-key embedding 401s (OpenAI / Voyage / Cohere rejecting the user's own API key) continue to escalate as actionable misconfiguration — covered by the new `does_not_classify_embedding_byo_key_401_as_session_expired` polarity guard. ## Test plan - [x] `cargo test classifies_embedding_api_invalid_token` — passes (new) - [x] `cargo test does_not_classify_embedding_byo_key` — passes (new polarity guard) - [x] `cargo test classifies_openhuman_invalid_token` — passes (4P0, unchanged) - [x] `cargo test does_not_classify_byo_key_provider` — passes (#2286 BYO-key contract preserved) - [x] `cargo test core::observability` — 91 tests pass, 0 regressions - [x] `cargo check --bin openhuman-core` — passes - [x] `cargo fmt --check` — clean --- src/core/observability.rs | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/core/observability.rs b/src/core/observability.rs index 9aeb4db30..702136cd0 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -239,6 +239,14 @@ pub fn expected_error_kind(message: &str) -> Option { /// keeps the #2286 contract intact: bare `"Invalid token"`, OpenAI / /// Anthropic BYO-key 401s, Discord upstream-bot-token rejections, and /// provider scope errors still route to Sentry as actionable. +/// - `"Embedding API error (401 Unauthorized): {…\"error\":\"Invalid token\"…}"` +/// — TAURI-RUST-4K5 (~118 events, escalating on 0.56.0). Same OpenHuman +/// backend session-expired envelope as 4P0, but the embedding client at +/// `src/openhuman/embeddings/openai.rs:139` wraps it with the +/// `"Embedding API error"` prefix instead of `"OpenHuman API error"`. +/// Uses the same conjunctive-anchor pattern so BYO-key embedding 401s +/// from third-party providers (OpenAI / Voyage / Cohere) still escalate +/// — guarded by `does_not_classify_embedding_byo_key_401_as_session_expired`. /// - `"SESSION_EXPIRED: backend session not active — sign in to resume LLM work"` /// — the `scheduler_gate::is_signed_out` sentinel from /// `providers::openhuman_backend::resolve_bearer`. @@ -266,6 +274,16 @@ pub fn is_session_expired_message(msg: &str) -> bool { // actionable in Sentry). || (msg.contains("OpenHuman API error (401") && msg.contains("\"error\":\"Invalid token\"")) + // TAURI-RUST-4K5 — same OpenHuman backend "Invalid token" envelope + // wrapped by `src/openhuman/embeddings/openai.rs:139` with the + // `"Embedding API error"` prefix instead of `"OpenHuman API error"`. + // Same conjunctive-anchor pattern as 4P0: the embedding-scoped + // prefix gates the match so a third-party BYO-key embedding 401 + // (e.g. OpenAI/Voyage/Cohere rejecting the user's own API key) + // stays actionable — guarded by + // `does_not_classify_embedding_byo_key_401_as_session_expired`. + || (msg.contains("Embedding API error (401") + && msg.contains("\"error\":\"Invalid token\"")) } /// Detect the in-process-core boot-window shape: a sibling component @@ -2507,6 +2525,71 @@ mod tests { ); } + /// TAURI-RUST-4K5 (118 events, escalating on 0.56.0): the embedding + /// client at `src/openhuman/embeddings/openai.rs:139` wraps the same + /// OpenHuman backend `{"success":false,"error":"Invalid token"}` 401 + /// envelope as 4P0, but with the `"Embedding API error"` prefix + /// instead of `"OpenHuman API error"` (different emit-site format + /// string, same underlying session-expired cause — see breadcrumb + /// `[scheduler_gate] signed_out false -> true` immediately preceding + /// the 401 in the event payload). + /// + /// Uses the same conjunctive `" (401"` + envelope-shaped + /// `"\"error\":\"Invalid token\""` anchor pattern as 4P0 so the + /// #2286 / BYO-key contract is preserved — covered by + /// `does_not_classify_byo_key_provider_401_as_session_expired` and + /// `does_not_classify_embedding_byo_key_401_as_session_expired` + /// (below). + #[test] + fn classifies_embedding_api_invalid_token_401_as_session_expired() { + // Verbatim wire shape from the TAURI-RUST-4K5 event payload (Sentry + // issue 5230, latest event 2026-05-27 20:49 on openhuman@0.56.0, + // domain=embeddings operation=openai_embed status=401). + let msg = + r#"Embedding API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#; + assert_eq!( + expected_error_kind(msg), + Some(ExpectedErrorKind::SessionExpired), + "TAURI-RUST-4K5 verbatim wire shape must classify as SessionExpired" + ); + + // The substring matcher must survive caller wrapping the same way + // the 4P0 web-channel `run_chat_task` test wraps the body — callers + // that re-emit through a tracing field or another layer prepend + // arbitrary context. + let wrapped = r#"openai_embed failed error=Embedding API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#; + assert_eq!( + expected_error_kind(wrapped), + Some(ExpectedErrorKind::SessionExpired), + "wrapped 4K5 envelope must still classify as SessionExpired" + ); + } + + /// Polarity guard for the 4K5 arm. The classifier must NOT swallow + /// `"Embedding API error (401 …)"` shapes from third-party BYO-key + /// embedding providers (OpenAI / Voyage / Cohere upstream rejecting + /// the user's own API key). Those are actionable user-config errors + /// that need to reach Sentry — same contract as + /// `does_not_classify_byo_key_provider_401_as_session_expired` for + /// the OpenAI chat API. + #[test] + fn does_not_classify_embedding_byo_key_401_as_session_expired() { + for raw in [ + "Embedding API error (401 Unauthorized): invalid_api_key", + r#"Embedding API error (401 Unauthorized): {"error":{"code":"invalid_api_key","message":"Incorrect API key provided"}}"#, + // Wire shape without the OpenHuman envelope — bare provider + // rejection prose. Must reach Sentry as actionable BYO-key + // misconfiguration. + "Embedding API error (401): authentication_error", + ] { + assert_eq!( + expected_error_kind(raw), + None, + "BYO-key embedding 401 must reach Sentry as actionable error: {raw}" + ); + } + } + #[test] fn does_not_classify_byo_key_provider_401_as_session_expired() { // Critical: a BYO-key 401 from OpenAI / Anthropic etc. is an From c04dcc3b8efdf9fd6fd7ba93fda33372b75f9227 Mon Sep 17 00:00:00 2001 From: Ghost Scripter Date: Thu, 28 May 2026 10:19:44 +0530 Subject: [PATCH 3/3] fix(observability): classify OpenHuman streaming 'Invalid token' 401 as SessionExpired (TAURI-RUST-1EE) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third emit-site prefix for the same OpenHuman backend `{"success":false,"error":"Invalid token"}` 401 envelope this PR already classifies for non-streaming chat (4P0) and embeddings (4K5). TAURI-RUST-1EE (Sentry issue 1807, 110 events, 109 on openhuman@0.56.0, domain=llm_provider operation=streaming_chat status=401 provider=OpenHuman) is the streaming-chat path: the body is wrapped at `inference/provider/compatible.rs:949` with the `"OpenHuman streaming API error"` prefix. The `streaming` token between `OpenHuman` and `API error` means the 4P0 anchor (`"OpenHuman API error (401"`) does not match it, so it needs its own prefix arm. Same conjunctive-anchor pattern as the existing arms — the OpenHuman-scoped streaming prefix gates the match so a third-party BYO-key streaming 401 (`"OpenAI streaming API error (401): invalid_api_key"`) stays actionable in Sentry. Tests: - `classifies_openhuman_streaming_invalid_token_401_as_session_expired` — verbatim 1EE wire shape (direct + caller-wrapped). - `does_not_classify_streaming_byo_key_401_as_session_expired` — polarity guard for the streaming prefix. ## Test plan - [x] `cargo test classifies_openhuman_streaming_invalid_token` — passes - [x] `cargo test does_not_classify_streaming_byo_key` — passes (polarity) - [x] `cargo test core::observability` — 93 tests pass, 0 regressions - [x] `cargo check --bin openhuman-core` — passes - [x] `cargo fmt --check` — clean --- src/core/observability.rs | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/core/observability.rs b/src/core/observability.rs index 702136cd0..353f412c6 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -247,6 +247,14 @@ pub fn expected_error_kind(message: &str) -> Option { /// Uses the same conjunctive-anchor pattern so BYO-key embedding 401s /// from third-party providers (OpenAI / Voyage / Cohere) still escalate /// — guarded by `does_not_classify_embedding_byo_key_401_as_session_expired`. +/// - `"OpenHuman streaming API error (401 Unauthorized): {…\"error\":\"Invalid token\"…}"` +/// — TAURI-RUST-1EE (~110 events, ongoing on 0.56.0). Same envelope as +/// 4P0, wrapped by the streaming-chat path at +/// `inference/provider/compatible.rs:949` with the +/// `"OpenHuman streaming API error"` prefix. The `streaming` token means +/// the 4P0 anchor doesn't match, so it needs its own prefix arm; BYO-key +/// streaming 401s still escalate — guarded by +/// `does_not_classify_streaming_byo_key_401_as_session_expired`. /// - `"SESSION_EXPIRED: backend session not active — sign in to resume LLM work"` /// — the `scheduler_gate::is_signed_out` sentinel from /// `providers::openhuman_backend::resolve_bearer`. @@ -284,6 +292,19 @@ pub fn is_session_expired_message(msg: &str) -> bool { // `does_not_classify_embedding_byo_key_401_as_session_expired`. || (msg.contains("Embedding API error (401") && msg.contains("\"error\":\"Invalid token\"")) + // TAURI-RUST-1EE — same OpenHuman backend "Invalid token" envelope + // wrapped by the streaming-chat path at + // `inference/provider/compatible.rs:949` with the + // `"OpenHuman streaming API error"` prefix. The `streaming` token + // between `OpenHuman` and `API error` means the 4P0 anchor + // (`"OpenHuman API error (401"`) does not match it, so the + // streaming path needs its own prefix arm. Same conjunctive-anchor + // pattern keeps third-party BYO-key streaming 401s + // (`"OpenAI streaming API error (401): invalid_api_key"`) + // escalating — guarded by + // `does_not_classify_streaming_byo_key_401_as_session_expired`. + || (msg.contains("OpenHuman streaming API error (401") + && msg.contains("\"error\":\"Invalid token\"")) } /// Detect the in-process-core boot-window shape: a sibling component @@ -2565,6 +2586,58 @@ mod tests { ); } + /// TAURI-RUST-1EE (Sentry issue 1807, 110 events, 109 on + /// openhuman@0.56.0): the streaming-chat path wraps the same OpenHuman + /// backend `{"success":false,"error":"Invalid token"}` 401 envelope + /// with the `"OpenHuman streaming API error"` prefix (emitted at + /// `inference/provider/compatible.rs:949`) — distinct from the + /// non-streaming `"OpenHuman API error"` prefix (4P0) and the + /// `"Embedding API error"` prefix (4K5). The `streaming` token between + /// `OpenHuman` and `API error` means the 4P0 anchor + /// (`"OpenHuman API error (401"`) does not match it, so it needs its + /// own prefix arm. + #[test] + fn classifies_openhuman_streaming_invalid_token_401_as_session_expired() { + // Verbatim wire shape from the TAURI-RUST-1EE event payload + // (domain=llm_provider operation=streaming_chat status=401 + // provider=OpenHuman model=reasoning-v1). + let msg = r#"OpenHuman streaming API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#; + assert_eq!( + expected_error_kind(msg), + Some(ExpectedErrorKind::SessionExpired), + "TAURI-RUST-1EE verbatim streaming wire shape must classify as SessionExpired" + ); + + // Caller-wrapped (agent.run_single / web_channel.run_chat_task + // re-emit prepends context) must still classify. + let wrapped = r#"run_chat_task failed error=OpenHuman streaming API error (401 Unauthorized): {"success":false,"error":"Invalid token"}"#; + assert_eq!( + expected_error_kind(wrapped), + Some(ExpectedErrorKind::SessionExpired), + "wrapped 1EE streaming envelope must still classify as SessionExpired" + ); + } + + /// Polarity guard for the 1EE streaming arm — a third-party BYO-key + /// provider's streaming 401 (`"OpenAI streaming API error (401 …): + /// invalid_api_key"`) must STILL reach Sentry as actionable + /// misconfiguration. The `"OpenHuman streaming API error (401"` prefix + /// gate keeps the match OpenHuman-scoped. + #[test] + fn does_not_classify_streaming_byo_key_401_as_session_expired() { + for raw in [ + "OpenAI streaming API error (401 Unauthorized): invalid_api_key", + r#"OpenAI streaming API error (401 Unauthorized): {"error":{"code":"invalid_api_key","message":"Incorrect API key provided"}}"#, + "Anthropic streaming API error (401): authentication_error", + ] { + assert_eq!( + expected_error_kind(raw), + None, + "BYO-key streaming 401 must reach Sentry as actionable error: {raw}" + ); + } + } + /// Polarity guard for the 4K5 arm. The classifier must NOT swallow /// `"Embedding API error (401 …)"` shapes from third-party BYO-key /// embedding providers (OpenAI / Voyage / Cohere upstream rejecting