fix(team): preserve anyhow cause chain in backend RPC error envelopes (OPENHUMAN-TAURI-AD)#1647
Conversation
… (OPENHUMAN-TAURI-AD)
`team::ops::get_authed_value` renders the inbound `anyhow::Error` with
plain `e.to_string()`, which only emits the outermost context and drops
every wrapped cause. The HTTP layer wraps the underlying reqwest error
with `.context(format!("backend request {} {}", …))` in
`api/rest.rs::authed_json`, so the chain at the failure site looks like:
outer: "backend request GET /teams"
cause: <reqwest::Error — connect timeout, DNS failure, TLS handshake,
non-2xx status response, …>
`e.to_string()` renders only the outer label, so Sentry events for
OPENHUMAN-TAURI-AD all read `backend request GET /teams` with no
underlying cause — undiagnosable. The reported event has
`elapsed_ms=49`, which is far too short for a real network timeout: the
true cause (immediate connection refused? token rejected mid-request?
URL parse error?) is the only signal worth surfacing, and it is
exactly what the chain carries.
This is the same failure mode the `report_error` doc-string in
`core/observability.rs` calls out (referencing OPENHUMAN-TAURI-B2) —
and the same one anyhow's alternate format specifier `{e:#}` is
designed for: outer context + every wrapped cause, joined by ": ".
Switch both `map_err` sites in `get_authed_value` to `{e:#}`. Because
every team RPC (`list_teams`, `get_team`, `list_members`, `get_usage`,
`create_team`, `update_team`, `delete_team`, `switch_team`,
`leave_team`, `join_team`) routes through this one helper, the single
fix covers the whole domain.
No new tests: the `{e:#}` rendering contract is already covered by
`core::observability::tests::anyhow_chain_is_rendered_in_full`, and
behavioural coverage here would require a synthetic reqwest failure
fixture — disproportionate for a single-helper format-specifier fix.
|
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 (1)
📝 WalkthroughWalkthrough
ChangesBackend error diagnostics
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Summary
map_errsites inteam::ops::get_authed_valuefrom plaine.to_string()toformat!(\"{e:#}\").report_errordoc-string incore/observability.rscalls out this exact failure mode (referencing OPENHUMAN-TAURI-B2): "Without this, anyhow's defaultto_string()only emits the outermost context and the underlying cause is dropped — making the captured Sentry event undiagnosable."This is not a Sentry-skip — it's the inverse. The current Sentry events are too vague to act on; the fix makes them actionable so we can diagnose the actual root cause.
Fixes OPENHUMAN-TAURI-AD (2 occurrences on
openhuman@0.53.35, Windows user in St. Petersburg, Russia).Why the chain matters here
api::rest::authed_jsonwraps the underlying reqwest error with.context(format!(\"backend request {} {}\", method, path)). The anyhow chain at the failure site is:\"backend request GET /teams\"reqwest::Error— could be connect timeout, DNS failure, TLS handshake error, immediate connection refused, non-2xx HTTP status, …e.to_string()renders only the outer label. The Sentry events all read\"backend request GET /teams\"with no underlying cause — andelapsed_ms=49is way too short for a real network timeout, so the true cause (immediate refusal? token rejected mid-request? URL parse error?) is the only signal worth surfacing.{e:#}(anyhow's alternate format) joins the outer context plus every wrapped cause with\": \", so the next event will tell us why the request failed and we can fix the underlying condition.Why fix once in
get_authed_valueEvery team RPC (
list_teams,get_team,list_members,get_usage,create_team,update_team,delete_team,switch_team,leave_team,join_team) routes through this one helper, so a single change covers the whole domain — no per-handler fanout needed.Test plan
cargo check --lib— no new warnings.cargo fmt.{e:#}rendering contract is already covered bycore::observability::tests::anyhow_chain_is_rendered_in_full. Behavioural coverage here would require a synthetic reqwest failure fixture — disproportionate for a single-helper format-specifier fix.Follow-up
Once this ships and the next Sentry event lands with the cause chain visible, a targeted follow-up PR can address the underlying condition. The 49ms elapsed time argues against "network unreachable" (PR #1601 would silence those once their full message includes
\"error sending request for url\"via this chain) — more likely an immediate transport refusal or auth boundary, both of which warrant their own handling.Summary by CodeRabbit
Bug Fixes
Documentation