Skip to content

fix(sentry): stop dropping the errors behind "contact support" on sends - #2738

Merged
innolope-dev merged 1 commit into
devfrom
fix/send-failure-telemetry
Aug 18, 2026
Merged

fix(sentry): stop dropping the errors behind "contact support" on sends#2738
innolope-dev merged 1 commit into
devfrom
fix/send-failure-telemetry

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Why

A user hit the generic "There was an issue with your request. Please contact support." toast on a $1 send. The failure left no trace: nothing in the backend (every request from her IP was 2xx), nothing on chain (no UserOperation), nothing in PostHog, nothing in Sentry.

Native reporting itself is fine — her device (environment:native, release 60e0e52 = mobile-release 1.0.43) delivered 6 Sentry events 20 minutes before the failed send, and native produces ~980 events/day. The blind spot is specific to the send path:

  1. The only Sentry path was a console.error. handleSendUserOpEncoded does console.error('Error sending UserOp:', error) and only calls captureException on the stale-webauthn-key branch; capturePasskeySignFailure returns early for anything that isn't a WebAuthn DOMException and posts to PostHog only. So the report depended entirely on captureConsoleIntegration.
  2. beforeSend then deleted it. viem's HttpRequestError message carries Details: Failed to fetch, which the networkIssues filter matches. Zero events matching Failed to fetch / Load failed / Network Error exist org-wide in the last 14 days, across every environment — the filter is absolute, and it eats real payment failures along with the noise. The same string is why the UI said "contact support": friendlyError doesn't map it either, so it falls through to genericSupport.
  3. No offline transport on native. A WebView that can't reach the ZeroDev bundler can't reach ingest.us.sentry.io either, so even an unfiltered event died with the session.

What changed

  • src/utils/sentry-critical-flow.ts (new)CRITICAL_FLOW_TAG + criticalFlowTags(flow). Its own module because the root sentry.utils.ts is loaded by the server and edge configs and must not reach into app-layer code.
  • sentry.utils.ts — events carrying the tag skip the noise filters. User cancellations stay filtered even for tagged events: someone backing out of the passkey sheet is not a defect and would drown out the real failures.
  • useDirectSendFlow.ts — the catch now reports: captureException with the failing step (create-charge / send-money / record-payment), charge id, recipient, amount and user id, plus a send_failed PostHog event. Every "contact support" toast now leaves one queryable record.
  • instrumentation-client.ts — native init uses makeBrowserOfflineTransport, so undeliverable envelopes park in IndexedDB and flush on a later launch.
  • sentry.utils.test.ts — covers the exemption and the cancellation carve-out.

Notes / follow-ups

  • For error classes that aren't filtered today (e.g. UserOperationExecutionError), the console-capture event and the new explicit capture will both fire and group separately. I left the console.error in place deliberately — handleSendUserOpEncoded is also called from link creation, kernel migration and card-signature repair, none of which capture explicitly, so suppressing it globally would lose coverage there.
  • Only the direct-send flow is instrumented here. withdraw/crypto, qr-pay and the contribute-pot flow have the same shape (catch → friendly copy → silence) and should get the same treatment; happy to do them in a follow-up or fold them in if you'd rather have one pass.
  • This ships to native only via a build; the filter change helps web immediately.

Testing

  • jest sentry.utils.test.ts — 49 passed.
  • prettier --check clean on all touched files; tsc --noEmit reports no errors in the touched files.

A user hit the generic 'contact support' toast on a $1 send and the failure
left no trace in Sentry, PostHog, or the backend. Native reporting works — her
device delivered six events twenty minutes earlier — but nothing in the send
path could produce a report:

- The only Sentry path was console.error in handleSendUserOpEncoded picked up
  by captureConsoleIntegration. beforeSend then dropped it: viem's
  HttpRequestError carries 'Details: Failed to fetch', which the networkIssues
  filter matches. Zero such events exist org-wide in 14 days, so the filter is
  absolute — it eats real payment failures along with the noise.
- The flow-level catch mapped the error to copy and stopped there. No
  captureException, no analytics; capturePasskeySignFailure returns early for
  everything that isn't a WebAuthn DOMException.
- The native init has no offline transport, so a WebView that can't reach the
  bundler can't deliver the report of that failure either.

Tag explicit captures from money-moving flows and exempt them from the noise
filters (cancellations stay filtered), report the direct-send failure at the
flow level with the step and charge id, and buffer native envelopes to
IndexedDB so they flush on a later launch.
@innolope-dev innolope-dev self-assigned this Aug 18, 2026
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 18, 2026 4:16pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca78c6b2-4336-4c01-b505-ea5ab613e149

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7167.28 → 7170.08 (+2.8)
Findings: 0 net (+11 new, -11 resolved)

🆕 New findings (11)

  • high hotspot — src/constants/analytics.consts.ts — 46 commits, +376/-14 lines since 6 months ago
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 31.8, SLOC 191
  • medium high-mdd — src/features/payments/flows/direct-send/useDirectSendFlow.ts:30 — useDirectSendFlow: MDD 60.0 (uses across many lines from declarations)
  • medium structural-dup — features/payments/flows/contribute-pot/useContributePotFlow.ts:59 — 33 duplicate lines / 136 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:56
  • medium high-dlt — src/features/payments/flows/direct-send/useDirectSendFlow.ts:30 — useDirectSendFlow: DLT 31 (calls 31 distinct functions — high context load)
  • medium complexity — src/features/payments/flows/direct-send/useDirectSendFlow.ts — CC 25, MI 50.23, SLOC 179
  • medium high-mdd — src/features/payments/flows/direct-send/useDirectSendFlow.ts:117 — : MDD 24.1 (uses across many lines from declarations)
  • low structural-dup — features/payments/flows/contribute-pot/useContributePotFlow.ts:95 — 18 duplicate lines / 52 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:92
  • low high-dlt — src/features/payments/flows/direct-send/useDirectSendFlow.ts:117 — : DLT 17 (calls 17 distinct functions — high context load)
  • low structural-dup — features/payments/flows/contribute-pot/useContributePotFlow.ts:217 — 11 duplicate lines / 53 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:168
  • low missing-return-type — src/features/payments/flows/direct-send/useDirectSendFlow.ts:30 — useDirectSendFlow: exported fn missing return type annotation

✅ Resolved (11)

  • src/constants/analytics.consts.ts — 45 commits, +373/-14 lines since 6 months ago
  • src/constants/analytics.consts.ts — CC 1, MI 31.86, SLOC 190
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts:26 — useDirectSendFlow: MDD 62.4 (uses across many lines from declarations)
  • features/payments/flows/contribute-pot/useContributePotFlow.ts:59 — 33 duplicate lines / 136 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:52
  • features/payments/flows/contribute-pot/useContributePotFlow.ts:201 — 27 duplicate lines / 86 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:142
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts — CC 24, MI 52.05, SLOC 159
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts:26 — useDirectSendFlow: DLT 28 (calls 28 distinct functions — high context load)
  • features/payments/flows/contribute-pot/useContributePotFlow.ts:95 — 18 duplicate lines / 52 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:88
  • features/payments/flows/contribute-pot/useContributePotFlow.ts:169 — 15 duplicate lines / 53 tokens with features/payments/flows/direct-send/useDirectSendFlow.ts:117
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts:113 — : MDD 11.6 (uses across many lines from declarations)
  • src/features/payments/flows/direct-send/useDirectSendFlow.ts:26 — useDirectSendFlow: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/sentry-critical-flow.ts 0.0 1.9 +1.9
src/features/payments/flows/direct-send/useDirectSendFlow.ts 10.6 11.4 +0.8

@innolope-dev
innolope-dev merged commit 2d42d13 into dev Aug 18, 2026
17 of 18 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 3176 ran, 0 failed, 0 skipped, 59.6s

📊 Coverage (unit)

metric %
statements 67.4%
branches 52.3%
functions 57.9%
lines 68.2%
⏱ 10 slowest test cases
time test
4.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.3s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.4s src/components/Global/IframeWrapper/__tests__/IframeWrapper.test.tsx › handles its OWN iframe's completion and ToS messages
0.3s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/utils/__tests__/auth-token.test.ts › is none — never guarded — when only the guarded marker is present
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/hooks/__tests__/useCrispTokenId.test.ts › retries then stays undefined when the endpoint keeps failing (no fallback token)
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

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.

1 participant