fix(observability): suppress composio-direct HTTP 403 Sentry noise (TAURI-RUST-322) (#2930)

This commit is contained in:
Cyrus Gray
2026-05-29 19:01:33 +05:30
committed by GitHub
parent 981094628b
commit 268de993c8
+99 -1
View File
@@ -1146,8 +1146,28 @@ fn is_provider_user_state_message(lower: &str) -> bool {
//
// Drops Sentry TAURI-RUST-X9 (~15.7 k events / ~22 h, single user,
// release openhuman@0.54.0+c25fc8e5fd3e).
//
// TAURI-RUST-322 (#2929): same direct-mode path but the Composio v3
// `/connected_accounts` API returns HTTP 403 instead of 401. This
// happens when the BYO API key exists and is syntactically valid but
// does not carry the `connected_accounts:read` permission (e.g. a
// scoped or legacy key). Wire shape:
//
// `[composio-direct] list_connections failed: Composio v3
// connected_accounts failed: HTTP 403`
//
// 403 from Composio v3 is a user-state condition (key permissions),
// not a bug in openhuman_core. Sentry has no remediation path — the
// user must regenerate their key with the correct scopes on
// app.composio.dev. The polling layer retries every 5 s and the UI
// already surfaces the error; flooding Sentry with 1,000+ events per
// user adds no signal.
//
// Drops Sentry TAURI-RUST-322 (1,021 events, multi-release).
if lower.contains("[composio-direct]")
&& (lower.contains("http 401") || lower.contains("invalid api key"))
&& (lower.contains("http 401")
|| lower.contains("http 403")
|| lower.contains("invalid api key"))
{
return true;
}
@@ -3842,6 +3862,84 @@ mod tests {
);
}
// ── TAURI-RUST-322 (#2929): composio-direct 403 (key missing perms) ─
#[test]
fn classifies_composio_direct_403_as_provider_user_state() {
// Canonical Sentry TAURI-RUST-322 wire shape — the verbatim
// title body from the issue (1,021 events, multi-release). The
// Composio v3 `/connected_accounts` endpoint returns HTTP 403
// when the BYO API key exists but lacks `connected_accounts:read`
// permission. This is a user-state condition; Sentry has no
// remediation path.
let msg = "[composio-direct] list_connections failed: \
Composio v3 connected_accounts failed: HTTP 403";
assert_eq!(
expected_error_kind(msg),
Some(ExpectedErrorKind::ProviderUserState),
"composio-direct HTTP 403 must demote to ProviderUserState (TAURI-RUST-322)"
);
}
#[test]
fn classifies_composio_direct_403_for_other_ops() {
// The `[composio-direct]` + `HTTP 403` arm must cover every op
// that can hit a 403 from the Composio v3 tenant (list_tools
// prefetch, authorize, etc.) — not just list_connections.
let shapes = [
// list_tools prefetch of connections hits the 403 wall
"[composio-direct] list_tools: prefetch connections failed: \
Composio v3 connected_accounts failed: HTTP 403",
// list_connections itself (the primary source of the leak)
"[composio-direct] list_connections (direct) failed: \
Composio v3 connected_accounts failed: HTTP 403",
// any future direct-mode op that hits a 403
"[composio-direct] composio_list_connections (direct) failed: \
Composio v3 connected_accounts failed: HTTP 403",
];
for msg in shapes {
assert_eq!(
expected_error_kind(msg),
Some(ExpectedErrorKind::ProviderUserState),
"every [composio-direct] op with HTTP 403 must demote to ProviderUserState: {msg}"
);
}
}
#[test]
fn does_not_classify_unrelated_http_403_as_composio_direct_user_state() {
// Discrimination test: a 403 that does NOT carry the
// `[composio-direct]` prefix must NOT match this arm. Backend-mode
// composio 403s and unrelated 403s must remain visible in Sentry.
let backend_403 = "[composio] list_connections failed: \
Backend returned 403 Forbidden for GET \
https://api.tinyhumans.ai/agent-integrations/composio/connections";
// The backend-mode shape passes through `is_backend_user_error_message`
// (4xx matcher), not this arm. Verify it does NOT match this arm.
assert!(
!lower_contains_composio_direct_auth_wall(backend_403),
"backend-mode 403 must NOT match the composio-direct arm"
);
let unrelated_403 = "GitHub API error: HTTP 403: rate limit exceeded";
assert_ne!(
expected_error_kind(unrelated_403),
Some(ExpectedErrorKind::ProviderUserState),
"unrelated 403 (no [composio-direct] anchor) must NOT match the composio-direct arm"
);
}
// Helper used only in the discrimination test above — mirrors the
// exact condition in `is_provider_user_state_message` without
// requiring access to the private function.
fn lower_contains_composio_direct_auth_wall(msg: &str) -> bool {
let lower = msg.to_ascii_lowercase();
lower.contains("[composio-direct]")
&& (lower.contains("http 401")
|| lower.contains("http 403")
|| lower.contains("invalid api key"))
}
// ── TAURI-RUST-34H: backend-wrapped Cloudflare anti-bot interstitial ─
#[test]