Repository navigation
fix(sentry): give stackless console events a stack and stop issue explosion - #2810
Conversation
…losion
Three related defects surfaced by PostHog's $cymbal_errors filter, which
flags $exception events arriving with an empty exception list. Those events
were reaching Sentry fine — but as message events with no frames, and with
grouping keys that forked a new issue per URL variant and per device.
captureConsoleIntegration only calls captureException when one of the console
args is an Error instance; otherwise it calls captureMessage, which carries no
stack. Set attachStacktrace so every message event gets one, including the
deliberate captureMessage calls in fetchWithSentry and native-auth-capture.
That covers third-party console noise too, where we cannot touch the call site.
A stack cannot recover a payload the call site already destroyed, so fix the
first-party sites that discarded theirs: SetupPasskey passed err.name and
err.message instead of err (the largest family, ~126/wk); qr-scanner rejects
with bare strings; the Sumsub loader had no Error at all; and the crypto
withdraw and useSendMoney handlers were logging [object Object] in
money-moving flows. Adds utils/to-error, replacing what were already three
ad-hoc copies of the same normalization, and serializing plain objects so the
shape survives into the title.
sanitizeUrl normalized numeric query values but not the rest, so one 429 on
/bridge/exchange-rate was three issues (accountType=iban/clabe/gb). Normalize
every value, with a lookahead so already-correct fingerprints keep their
existing issue instead of forking on deploy. Anchoring the numeric rule fixes
a bug it caught: unanchored, it ate the leading 0 of an 0x-prefixed address
and left {id}xaf88d065, so every wallet still got its own fingerprint.
OneSignal inlines a per-device onesignalId into its op-failure message, minting
a Sentry issue per device. Collapse those onto one fingerprint per op name
rather than dropping them — push-subscription writes failing is the signal that
was missing while iOS push was dead on a mis-provisioned APNs key. Its worker
messenger's no-service-worker warning is pure noise and is ignored.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
On-demand reviews are free for the next 27 days. After that, they cost $0.25 per reviewed file. Or wait 31 minutes for your next included review. View limit detailsLimit details: You’ve used all 2 included reviews currently available. Your 55 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
Comment |
|
@coderabbitai review |
|
Code-analysis diffPainscore total: 7425.47 → 7431.14 (+5.67) 🆕 New findings (41)
…and 21 more. ✅ Resolved (39)
…and 19 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
#2810 added collapseNoisyFingerprint at the same spot this branch added isTransientCapgoNoise. Independent additions, both kept: Capgo's transient updater logs are suppressed in shouldIgnoreError, OneSignal's per-device op failures are re-fingerprinted in beforeSendHandler.
What
Started from this PostHog view —
$exceptionevents where$cymbal_errorsis set. That property is PostHog's flag for "empty exception list": the event arrived with a message but noexception.values, so no type and no stack.Those events were reaching Sentry fine. The problem is the shape they arrived in, and how they grouped. 433 events / 255 people over 7d (~3.4% of exception volume).
Why they were stackless
captureConsoleIntegrationonly callscaptureExceptionwhen one of the console args is anErrorinstance; otherwise it callscaptureMessage, which carries no stack at all (captureconsole.js). Every event on that page was either aconsole.errorwhose args held noError, or one of our deliberatecaptureMessagecalls.Changes
1.
attachStacktrace: true— synthesizes a stack for every message event, includingfetchWithSentry/native-auth-capture, and third-party console noise where we can't touch the call site. One line, covers all 433.2. Call sites that destroyed their payload — a stack can't recover an error the call site already threw away:
SetupPasskey.tsxerr.name, err.messageinstead oferr— largest family, ~126/wkuseQRScanner.ts×2'Camera not found.')SumsubKycWrapper.tsxErrorobject at allwithdraw/crypto/page.tsx[object Object]— money-moving flowuseSendMoney.ts[object Object]— money-moving flowAdds
utils/to-error.ts, replacing what were already three ad-hoc copies of the same normalization. It JSON-serializes plain objects so the shape survives into the title instead of collapsing.Passkey filtering is unchanged — the
AbortError/not allowed by the uservariants still match, via exception type rather than message.3.
sanitizeUrlfingerprint gap — it normalized numeric query values but not the rest, so one upstream 429 was three separate issues:exchange-rate?accountType=ibanexchange-rate?accountType=clabeexchange-rate?accountType=gbNow normalizes every value. A lookahead skips values the numeric pass already replaced, so fingerprints that were already correct keep their existing issue rather than forking a new one on deploy.
While adding tests for this I found a pre-existing bug: the numeric rule was unanchored, so
?address=0xaf88d065had its leading0replaced and became{id}xaf88d065— every wallet still got its own fingerprint and the value was unreadable. Anchored with(?=&|$).4. OneSignal issue explosion — its op-failure message inlines a per-device
onesignalId, so every device minted its own Sentry issue. These are fingerprinted, not dropped: push-subscription writes failing is exactly the signal that was missing while iOS push was dead on a mis-provisioned APNs key. Collapsed onto one issue per op name. The worker messenger's no-service-worker warning is pure noise and is ignored.Note
attachStacktracelands onthreads, notexception.values, so PostHog's mirror will still flag the remaining third-party ones as an empty exception list. That flag only clears where we now pass a realError. Sentry is fully fixed; the PostHog view shrinks but won't empty out.Testing
to-error.test.ts(8 cases),sentry-fingerprint-url.test.ts(7 cases, incl. the no-refingerprint regression guard)sentry.utils.test.tswith the OneSignal fingerprint + ignore casestoErrortests and 272 tests across the touched components pass; prettier and eslint cleanOverlap
Touches
sentry.utils.ts, as does #2735 (chained-error matching + Capgo noise). No textual conflict — different regions, andcollapseNoisyFingerprintcomposes with itsshouldIgnoreErrorchanges — but whichever lands second deserves a re-read.