Skip to content

fix(sentry): stop reporting expected 404s, and group the rest by route - #2585

Draft
Hugo0 wants to merge 1 commit into
devfrom
fix/sentry-address-fingerprint-flood
Draft

fix(sentry): stop reporting expected 404s, and group the rest by route#2585
Hugo0 wants to merge 1 commit into
devfrom
fix/sentry-address-fingerprint-flood

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

/ens/reverse produced 1,948 Sentry events in 24 hours on 2026-07-29 — 15 new issues in a single hour, each holding one address. It buried every other signal: in one monitor sweep it accounted for 15 of 15 new issues. A monitor whose feed is entirely one benign signal is one bad hour from missing something real.

Two separate causes, and the one named in the title is the smaller of them.

1. The 404 is an answer, not a failure — this is the primary fix

usePrimaryNameServer asks the server for a primary name and falls back to a client-side lookup when that misses, so a 404 costs the user nothing. And the route isn't deployed at all: /ens/reverse/:address exists on neither main nor dev in peanut-api-ts (src/routes/ens/index.ts registers only /ens/:ensName), because peanut-api-ts#1237 is still open. So every address rendered by AddressLink and TransactionCard reports one.

/rhino/status/:addr 404 is the same shape — a poll-until-present loop where 404 is the normal waiting tick, once per poll per address.

Both now skip reporting. Timeouts and 5xx on those routes still report — those are real signal, and the 20s timeouts have been genuine.

2. What did get reported split per address

sanitizeUrl feeds setFingerprint, but only collapsed numeric ids and UUIDs — a hex segment survived verbatim, so every address minted its own fingerprint. It now collapses 0x runs too (addresses, tx hashes, send-link pubKeys) as a generic path-segment rule, not a per-route allowlist, so the sibling routes are covered by construction.

The messages carried the raw URL too, and that mattered more than it looks: captureConsoleIntegration({ levels: ['error','warn'] }) turns the console.warn into a second Sentry event with no fingerprint at all, grouped on message text. Fixing only the fingerprint would have halved the flood, not stopped it. All three message sites now use the sanitized URL.

Debuggability is preserved

The real URL was already attached as extra.url and still is — a test pins that. Nothing gets harder to investigate; you lose only the per-address issue explosion.

Risks

  • The sharp edge is an over-broad skip rule hiding a real regression. Mitigated by scoping each rule to an exact (route, status) pair and leaving 5xx/timeouts reporting — with tests asserting a 500 on those same routes still reports.
  • Ordering: hex is matched before the numeric rule, since an all-digit hex body would otherwise become /0x{id} and split the group again. Pinned by a test.
  • sanitizeUrl is promoted from a closure to an exported function — it was untestable before, and is now unit-tested. No behaviour change for existing callers beyond the added rule.
  • peanut-ui only. No cross-repo change, and deliberately independent of whether Prod to staging #1237 ships.

QA

npx jest src/utils/__tests__/sentry.utils.test.ts — 12 new cases, all passing.
Full suite: 178 suites / 2330 tests, 0 failures. npm run typecheck clean.

Key cases: /ens/reverse + /rhino/status 404 report neither to Sentry nor to console.warn · a 500 on those routes still reports · two different addresses produce an identical message (the whole point) · extra.url keeps the real URL · hex/UUID/numeric collapse · /ens/vitalik.eth untouched · all-digit hex body ordering guard.

Screenshots: N/A — observability plumbing, no rendered change.

Follow-up (not this PR)

usePrimaryNameServer calls a route that has never existed in production, so every ENS lookup pays a wasted round-trip before falling back. Either land #1237 or drop the server-first call. Worth its own ticket — this PR just stops the noise either way.

/ens/reverse produced 1,948 Sentry events in 24h on 2026-07-29 — 15 new
issues in one hour, each a single address, burying every other signal in the
feed. Two separate causes, and the obvious one is the smaller one.

First: the 404 is not a failure. usePrimaryNameServer asks the server, then
falls back to a client-side lookup, so the user never sees anything. The route
isn't even deployed (peanut-api-ts #1237 is still open), so every address
rendered by AddressLink and TransactionCard reports one. /rhino/status/404 is
the same shape: a poll-until-present loop where 404 is the waiting tick. Both
now skip reporting; timeouts and 5xx still report, because those are real.

Second: what did get reported split per address. sanitizeUrl feeds the
fingerprint but only collapsed numeric ids and UUIDs, so a hex segment
survived and every address minted its own group. It now collapses 0x runs
too — addresses, tx hashes and send-link pubKeys, generically, rather than a
per-route allowlist.

The message carried the raw URL as well, and that matters more than it looks:
captureConsoleIntegration turns the console.warn into a second Sentry event
with no fingerprint at all, grouped on message text. Fixing only the
fingerprint would have halved the flood, not stopped it. All three message
sites now use the sanitized URL; the real one stays on extra.url, so nothing
becomes harder to debug.

Hex is matched before the numeric rule — an all-digit hex body would
otherwise become '/0x{id}' and split the group again. A test pins that.
@vercel

vercel Bot commented Jul 29, 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 Jul 29, 2026 10:52pm

Request Review

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

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: 26071ea2-ef5a-4bd6-adf6-951e78719544

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: 6739.08 → 6739.19 (+0.11)
Findings: 0 net (+2 new, -2 resolved)

🆕 New findings (2)

  • medium high-mdd — src/utils/sentry.utils.ts:418 — fetchWithSentry: MDD 52.5 (uses across many lines from declarations)
  • low high-dlt — src/utils/sentry.utils.ts:418 — fetchWithSentry: DLT 27 (calls 27 distinct functions — high context load)

✅ Resolved (2)

  • src/utils/sentry.utils.ts:382 — fetchWithSentry: MDD 53.8 (uses across many lines from declarations)
  • src/utils/sentry.utils.ts:382 — fetchWithSentry: DLT 27 (calls 27 distinct functions — high context load)

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2333 ran, 0 failed, 0 skipped, 42.0s

📊 Coverage (unit)

metric %
statements 61.0%
branches 44.3%
functions 50.4%
lines 61.5%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.2s 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/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
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/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/utils/__tests__/auth-token.test.ts › returns the token hydrated from Preferences after authReady
0.2s src/utils/__tests__/demo-balance.test.ts › resetDemoBalance refills and restarts the TTL window
📍 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