Skip to content

feat(telemetry): report user-facing errors to the admin Errors page - #2674

Open
MusabShakeel576 wants to merge 3 commits into
qafrom
claude/affectionate-ramanujan-awlmzc
Open

MusabShakeel576 wants to merge 3 commits into
qafrom
claude/affectionate-ramanujan-awlmzc

Conversation

@MusabShakeel576

@MusabShakeel576 MusabShakeel576 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

The app now reports the errors users actually see to the new admin Errors page. Events are batched to POST <EXPO_PUBLIC_FLASH_API_BASE_URL>/accounts/v1/errors/ingest.

What is reported (lib/telemetry/)

  • Failed flows: every track() of a failure event, such as "Deposit Failed" or "Email Verification Failed".
    • The flow is inferred from the event name.
    • This happens regardless of the per-call amplitude option.
    • error_boundary is skipped because the boundary reports crashes itself.
  • Error toasts: Toast.show is wrapped, and every error toast is reported with the text the user saw.
  • Failed API calls: a global fetch wrapper covers calls to our 4 backend base URLs only, never to third parties or to the ingest endpoint.
    • Network failures and 5xx are reported, along with 4xx except 401, and 404 on GET/HEAD.
    • Only the path is sent: host, query string and fragment are dropped, and the backend cleans the path again.
  • Crashes: the ErrorBoundary reports the crash with the screen and the linked GlitchTip event id. A stale web bundle is reported as STALE_BUNDLE at info level, not as a crash.

How it's sent

  • errorIngestQueue flushes every 2 s or at 20 events, sending at most 50 per batch.
    • The queue holds up to 200 events.
    • Identical events within 5 s are deduped.
    • A failed batch is retried once, on 429/5xx/network errors only.
    • On web it flushes with keepalive on pagehide.
  • If a signed-in batch gets a 401, it is resent without credentials. The backend takes userId only from the session, never from the body.
  • Message text is redacted (keys, tokens, JWTs, emails) and truncated to the backend's limits before it leaves the device.
  • Inert in dev builds (__DEV__). Turn it off elsewhere with EXPO_PUBLIC_ERRORS_INGEST_ENABLED=false; it is on by default.

GlitchTip now knows the user. sentryUser sets the scope user to the backend user id and username, never the email, following useUserStore. beforeSend makes that id win over captures that still pass a Turnkey sub-org id inline. sendDefaultPii stays false, so no IP, headers or cookies are sent. This is a deliberate change from "no user identifiers in GlitchTip": it is how the Errors page links a crash to a user. Please confirm you're happy with it.

lib/utils/userFacingError.ts (and its test) is copied verbatim from master. It is not on qa yet, and the reporters use its redactSecrets, isNetworkError and userFacingErrorMessage. git diff origin/master -- lib/utils/userFacingError.ts is empty, so it will merge cleanly when master is next merged into qa.

Depends on

Solid-Money/solid-backend#1953 serves the ingest endpoint. Shipping this first is safe:

  • A 404 drops the batch without retrying.
  • A backend with the page switched off (ERRORS_ENABLED unset, as on prod) answers 202 { accepted: 0 }.

Testing

  • 134 new tests across 8 suites in lib/telemetry/__tests__ and lib/utils/__tests__, all passing. They cover the queue, redaction, flow inference, the fetch and toast reporters, and the Sentry user sync.
  • tsc: no new errors; the 9 pre-existing ones on qa remain.
  • eslint is clean on the changed files.
  • Full Jest suite: the only failure is the pre-existing MMKV suite, which also fails on qa.

🤖 Generated with Claude Code

https://claude.ai/code/session_017rhTzZBfBRybucitFXRFMW

claude added 3 commits October 8, 2026 13:51
Adds the app's half of the Errors page contract: `reportError()` builds a
`ClientErrorEvent` (uuid, device time, platform, app version, current
screen, Amplitude device id), strips credentials from the message and
the text the user saw, cuts every field to the contract limits and
queues it. The queue batches to POST /accounts/v1/errors/ingest every 2s
or at 20 events (at most 50 per request, 200 held, oldest dropped),
sends an identical kind+code+message+endpoint once per 5s, retries a
failed batch once and then drops it, and flushes when the app goes to
the background or the web page is hidden.

It sends with the app's normal auth (Bearer + X-Platform on native,
cookies on web) and resends a 401 once anonymously instead of
refreshing, so an expired session cannot loop with the error being
reported. Nothing is sent from dev builds, during web pre-rendering, or
when EXPO_PUBLIC_ERRORS_INGEST_ENABLED=false.

qa does not have lib/utils/userFacingError yet (master does, from the
Pimlico key leak fix); it is copied verbatim from master, with its test,
so the two merge cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rhTzZBfBRybucitFXRFMW
…crashes

- Failed flows: track() mirrors every error event (`*_failed`, `*_error`,
  `*_rejected`, `*_declined`, `*_blocked`, `*_unavailable`, plus
  region_unavailable_shown and qr_scanner_permission_denied) with its
  Title-Cased Amplitude name, a flow inferred from the name, and the
  reason read from whichever key the call site used. It does so even
  with `{ amplitude: false }`, which is about double-counting in
  Amplitude. The store-review and Trustpilot "unavailable" events are not
  app failures and stay out.
- Error toasts: `Toast.show` is wrapped once, so every `type: 'error'`
  toast is reported with exactly the text it showed, without touching
  the call sites. A cancelled prompt is reported as info.
- API failures: the global fetch is wrapped once. For our own services
  only (accounts, rewards, analytics, vault manager; never the ingest
  endpoint itself), a 4xx/5xx is reported with the backend's code and
  message read from a clone, and a connection failure is reported as a
  warning. The caller gets the same Response or the same error; other
  hosts get the original promise. 401s (the token refresh retries them)
  and 404s on reads (card status, Bridge customer, What's New and others
  answer "none" that way on every app open) are not failures.
- Crashes: the error boundary sends one crash event carrying the id
  Sentry.captureException returned, so the Errors page can link to
  GlitchTip; its error_boundary track is not mirrored a second time.

The installers are wired up in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rhTzZBfBRybucitFXRFMW
…he user

The root layout installs the fetch and toast reporters before the app
renders and keeps the current screen for error reports.

GlitchTip now knows who hit an error: it follows the selected user in
the store, which every sign-in path (login, signup, restored session,
account switch) ends at and every sign-out clears, and sets
`{ id: userId, username }` — the backend user id, never the Turnkey
suborg id or the email; sendDefaultPii stays off. Captures that still
pass `user: { id: suborgId }` inline replace the scope user for that
event, so beforeSend puts the signed-in backend id back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rhTzZBfBRybucitFXRFMW
@vercel

vercel Bot commented Oct 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
solid-app Ignored Ignored Oct 8, 2026 2:24pm UTC
solid-app-staging Ignored Ignored Oct 8, 2026 2:24pm UTC

Request Review

@claude

claude Bot commented Oct 8, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.


Review scope:

  • Bugs and logic errors in the diff
  • CLAUDE.md compliance (none exist in this repository)
  • Debugging leftovers (console.log, debugger, verbose logging)
  • Commented-out code blocks
  • Test/scaffolding artifacts (hardcoded values, TODO/FIXME/XXX, .only/.skip)
  • Secrets & local config (API keys, tokens, local URLs)
  • Accidental/unrelated edits

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants