Skip to content

chore(sentry): capture console errors only, stop reporting local builds and two expected 4xx - #3268

Merged
abalinda merged 7 commits into
devfrom
chore/sentry-hygiene
Sep 19, 2026
Merged

abalinda merged 7 commits into
devfrom
chore/sentry-hygiene

Conversation

@abalinda

@abalinda abalinda commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

peanut-ui sends about 31,000 Sentry events a month (last 30 days: production 10,561 error + 4,482 warning + 284 info; native 3,347 error + 3,428 warning + 2,958 info + 114 fatal; preview 2,649 error + 1,374 warning; development 336; CSP reports 1,165). Sentry bills every accepted event whatever its level, and archiving an issue does not stop the bill. This PR removes the events nobody reads.

1. Console capture listens on error only — src/utils/sentry-init.ts (web client), sentry.server.config.ts, sentry.edge.config.ts. A console.warn cost the same as an exception, and warn-level output here is handled conditions, not defects: the Radix "DialogContent requires a DialogTitle" notice (2,158 in 90 days), "Icon "upload" not found" (1,420), "error fetching tokenPrice, falling back to tokenDenomination" (3,320). A message captured with no Error object also arrives under a minified title ("d", "s", "Module.d", "iE"), so roughly 3,000 a month were unreadable. attachStacktrace and posthogErrorMirror() are unchanged. The native client (instrumentation-client.ts) was already ['error'].

2. Local builds stop reporting. New isSentryReportingEnvironment() in src/utils/sentry-env.ts gates client, server and edge on the inferred environment rather than on NODE_ENV. That is stricter than the guard it replaces: a next build on a laptop runs with NODE_ENV=production and no VERCEL_ENV, so it reported as production and was billed there — 336 events in 30 days, now zero.

preview deliberately keeps reporting. The OTA liveness proof in ops/native-ota-envless-bundle-rca.md reads preview and canary events out of Sentry, so a dark preview would take that check with it. The preview families this PR was expected to remove — the "OneSignal configuration missing" warnings (1,116 in 30 days), the reading 'waiting' TypeError (2,080), "[sumsub] failed to load websdk script" (388) — therefore stay. Change 1 still removes the warn-level share of them.

3. Server tracesSampleRate 1 → 0.1, matching the client.

4. Two expected non-2xx responses stop reporting (findSkipRule, src/utils/sentry.utils.ts):

  • /invites/validate 409 — the code resolves to a campaign only, which validateInviteCode already reads as a success (typedCampaignOnly).
  • /perks/pending 401 — a stale session. The skip rule already covered the fetch layer; the leak was src/services/perks.ts calling console.error with a non-Error at the call site, re-creating under a minified title the event fetchWithSentry had just suppressed. That line is console.info now, following the precedent already written into reportNonOkResponse.

5xx on both still reports, pinned by tests.

/bridge/exchange-rate 429 was in this list and has been taken back out — see Design notes. It reports, and a test now fails if anyone adds the rule back.

Task

None — Sentry cost hygiene, no tracking task exists.

Risks

  • Suppression is upstream of the PostHog mirror. findSkipRule runs in reportNonOkResponse before Sentry.captureMessage, and posthogErrorMirror() mirrors Sentry events into PostHog — so a skipped response disappears from both, not just from Sentry. ops/monitor/known-noise.md:226 is a PostHog-sourced /invites/validate bucket that assumes otherwise. True for the two remaining skips; both are benign.
  • Lower fidelity is the point. A defect that only ever announced itself through console.warn now reaches the browser console and nothing else. Nothing in the named noise sources is in that class.
  • A local next build no longer reports. It used to file into production, which is the reason to stop — but if you were relying on a local production build to surface an error in Sentry, it will not any more. Preview, staging, production and native all still report.
  • Server traces drop to 10%, so a p99 server span is sampled rather than certain.
  • No cross-repo action needed; no API contract change; no user-visible change.

Design notes / accepted trade-offs

  • /bridge/exchange-rate 429 was dropped from the skip table after review. Two reviewers landed on it independently and the code agreed with them. useGetExchangeRate (src/hooks/useGetExchangeRate.tsx:41-53) swallows the failure and returns a rate of '1' — it resolves, so react-query counts it a success and retry: 3 never fires, and '1' is cached for the 5-minute staleTime. bankWithdrawMinUsd then computes Math.ceil(50/1), so a Mexican bank withdrawal shows a $50 minimum instead of about $3, and parseFloat('1') > 0 passes isMinReady, so submission is not gated either. That 429 is the only alert for a wrong number on a money screen — and for the open FX-stampede P2 (ops/monitor/sentry-triage.md:522) whose runbook entry escalates on exactly this volume. The rule's stated justification was also wrong: the 429 comes from peanut-api's own global @fastify/rate-limit, which reports nothing server-side. It is replaced by a comment at the same spot saying why it must not be skipped, plus it.each([429, 500])('still reports …') so re-adding it fails the suite. The honest follow-up is the keyed single-flight fix in src/utils/no-cache.ts, not a Sentry rule.
  • The /invites/validate 409 rule is not errorCodes-scoped, unlike the qr-payment/init 409 rule beside it. It cannot be: the campaign-only response body carries no error field for the mechanism to match on. Today the route returns 409 for that outcome only (peanut-api-ts src/routes/invite.ts:445), so nothing reachable is suppressed — but a future, different 409 on this route would go quiet. Scoping it needs an error code on the backend response first.
  • The native transport canary and the OneSignal subscription snapshot are deliberately untouched. Both already write to PostHog, and the current v7 canary is failure-only: 14 Sentry events in 30 days. The ~2,400/month "direct-fetch canary …" messages and ~407/month "onesignal subscription snapshot" messages come from the retired canary in old native binaries, which no code change can stop — a Sentry inbound filter is the fix, and it is not this PR.
  • /invites/validate 400 and /perks/pending 401 were already in the skip table (added 2026-07-08 and 2026-02-05), so no duplicate rule was added for them. claimPerk's neighbouring console.error is left alone on purpose: a failed claim is money-adjacent and worth reporting.
  • Three code comments justified a console.info with "captureConsoleIntegration listens on ['error','warn']", which this PR makes false. Corrected in place; the decisions they explain are unchanged.
  • beforeSendHandler redaction, isPaymentNetworkExplorerPath and the CSP report route are untouched.

Smells: adds none. Reveals three, all left for follow-ups — flagged, not changed:

  1. There are two sentry.utils.ts files — one at the repo root, one in src/utils/ — and the second imports from the first (../../sentry.utils). Same name, related work, no way to guess which is which.
  2. sentry.server.config.ts and sentry.edge.config.ts are now byte-identical apart from tracesSampleRate. Shared options would remove the copy.
  3. console.error(<non-Error>, …) is a repo-wide pattern, and it is what produces the minified Sentry titles ("d", "Module.d", "iE"). src/utils/to-error.ts already exists for it; a lint rule is the systemic fix. This PR only touches the one instance it was sent to fix.

Docs follow-ups (separate, NOT in this PR)

A mono docs sweep found runbook drift this creates. None of it belongs in a code PR:

  • ops/monitor/known-noise.md — rows :47 (invites 409), :59 (perks 401), :65 and :133 (buckets that exist only because warn was captured), :81 (canary baseline).
  • ops/monitor/sentry-triage.md — the passkey-debug decisions at :279, :542, :797 recommend exactly this removal; the preview/development families at :140, :156, :1007 are structurally gone.
  • skills/monitor/SKILL.md, ops/monitor/log.md, ops/post-release-monitoring.md — the sweep diffs unresolved counts against a baseline this PR moves; needs a watermark so the next sweep reads "telemetry moved", not "telemetry improved".
  • engineering/testing/README.md:203-221, engineering/native/crash-analytics.md:3-15 — note that a local build no longer initializes Sentry, and that console.warn is no longer captured.

Legal: no impact. content/legal/privacy/en.md was read end to end. Nothing new leaves our systems — this PR only sends less to Sentry, adds no processor, no data category, no retention or deletion change. Sentry stays named at :77 and stays in use.

QA

  • npm test — 680 suites, 8,359 passed (5 skipped). npm run typecheck, pnpm prettier --check ., npm run build green locally.
  • src/utils/__tests__/sentry.utils.test.ts pins the rules both ways: /invites/validate 409 and /perks/pending 401 are skipped; /invites/validate 500, /manteca/qr-payment/init 500 and /bridge/exchange-rate 429 and 500 still report.
  • src/utils/__tests__/sentry-init.test.ts and src/features/payment-network-explorer/__tests__/sentryServerEdge.test.ts assert on client, server and edge that a preview build does call Sentry.init and a local build (no VERCEL_ENV) does not.

Screenshots: N/A (no visible change — telemetry configuration only).

🤖 Generated with Claude Code

peanut-ui sends ~31k Sentry error events a month and every accepted event is
billed regardless of level, so the cheapest wins are the events nobody reads.

captureConsoleIntegration listened on `warn`, which bills a console.warn like
an exception. Warn-level output here is handled conditions, not defects: the
Radix DialogTitle notice (2,158 in 90 days), a missing icon name (1,420), the
tokenPrice fallback (3,320). A message captured without an Error also arrives
with a minified title ("d", "Module.d", "iE"), so ~3k a month were unreadable.

Every build also reported into the production project. Ad-hoc PR previews
(2,649 error + 1,374 warning in 30 days) and local builds were billed and
mixed into production issues that nobody triages. Only production, staging and
native now init at all.

Server tracing ran at 100% while the client ran at 10%. Match them.
Each of these is an outcome the UI handles and shows the user, so an error in
Sentry says only that the product worked.

- /invites/validate 409: the code resolves to a campaign only, which
  validateInviteCode already reads as a success.
- /bridge/exchange-rate 429: the upstream quota doing its job. Every mounted
  rate hook retries, so one fault arrived as many events.
- /perks/pending 401: a stale session. The skip rule already covered it, but
  the call site's own console.error re-created the event under a minified
  title — that is the "Expected stale-session 401" known-noise row.

5xx on all three still reports, pinned by tests here and on
/manteca/qr-payment/init.
Sentry bills a captureMessage like a crash. None of these is a fault:

- The native transport canary already wrote the same verdict to PostHog as
  native_transport_canary_failed; the Sentry message was a paid duplicate. The
  per-probe error text that used to ride only on the Sentry event now rides on
  the PostHog one, so nothing is lost and the daily sampling it needed is gone.
- Passkey debug info (1,558 in 90 days at level info) is device capability
  state. In PostHog it is queryable against the passkey funnel. The
  captureException for a real failure stays.
- The OneSignal subscription snapshot reported its own read failure (~410 a
  month) as a stackless message. It now rides the same PostHog event as the
  snapshot itself, under snapshot_error.
@coderabbitai

coderabbitai Bot commented Sep 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: Repository: peanutprotocol/peanut-ui/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: a9116e0d-2e4c-4cd7-924d-8b228649c5bb

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 19, 2026 2:02pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 8459.51 → 8461.25 (+1.74)
Findings: 0 net (+11 new, -11 resolved)

🆕 New findings (11)

  • high hotspot — src/constants/analytics.consts.ts — 80 commits, +435/-82 lines since 6 months ago
  • high hotspot — src/utils/sentry.utils.ts — 57 commits, +1109/-396 lines since 6 months ago
  • medium high-mdd — src/utils/sentry.utils.ts:590 — fetchWithSentry: MDD 75.0 (uses across many lines from declarations)
  • medium high-mdd — src/utils/sentry-init.ts:47 — : MDD 24.4 (uses across many lines from declarations)
  • medium high-mdd — src/utils/sentry.utils.ts:512 — reportNonOkResponse: MDD 24.5 (uses across many lines from declarations)
  • medium complexity — src/utils/sentry-init.ts — CC 21, MI 62.81, SLOC 54
  • medium complexity — src/services/perks.ts — CC 8, MI 55.29, SLOC 50
  • low high-dlt — src/utils/sentry.utils.ts:590 — fetchWithSentry: DLT 29 (calls 29 distinct functions — high context load)
  • low high-dlt — src/utils/sentry.utils.ts:512 — reportNonOkResponse: DLT 19 (calls 19 distinct functions — high context load)
  • low unused-export — src/utils/sentry.utils.ts:403 — unused export: MIN_TRANSPORT_LEG_MS
  • low missing-return-type — src/utils/sentry.utils.ts:462 — sanitizeUrl: exported fn missing return type annotation

✅ Resolved (11)

  • src/constants/analytics.consts.ts — 78 commits, +430/-77 lines since 6 months ago
  • src/utils/sentry.utils.ts — 54 commits, +1087/-383 lines since 6 months ago
  • src/utils/sentry.utils.ts:581 — fetchWithSentry: MDD 75.0 (uses across many lines from declarations)
  • src/utils/sentry.utils.ts:503 — reportNonOkResponse: MDD 24.5 (uses across many lines from declarations)
  • src/utils/sentry-init.ts:46 — : MDD 21.2 (uses across many lines from declarations)
  • src/utils/sentry-init.ts — CC 20, MI 63.03, SLOC 53
  • src/services/perks.ts — CC 8, MI 55.26, SLOC 50
  • src/utils/sentry.utils.ts:581 — fetchWithSentry: DLT 29 (calls 29 distinct functions — high context load)
  • src/utils/sentry.utils.ts:503 — reportNonOkResponse: DLT 19 (calls 19 distinct functions — high context load)
  • src/utils/sentry.utils.ts:394 — unused export: MIN_TRANSPORT_LEG_MS
  • src/utils/sentry.utils.ts:453 — sanitizeUrl: exported fn missing return type annotation

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 8359 ran, 0 failed, 0 skipped, 3.1m

📊 Coverage (unit)

metric %
statements 80.3%
branches 68.4%
functions 75.0%
lines 81.5%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Scan that recovers on the retry lands on the payment screen, not an error
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

Scope correction after checking the current volumes. The native transport
canary and the OneSignal subscription snapshot are restored untouched: both
already write to PostHog, and their remaining Sentry volume (14 and ~407 a
month) comes from the retired canary in old native binaries, which no code
change can stop — a Sentry inbound filter will.

That leaves the passkey debug capture, which is live (SignTestTransaction
calls it on a failed test transaction) but reported device capability state as
a Sentry message at level info, 1,558 in 90 days. It is console-only now; the
captureException for a real failure is unchanged.
Three comments justified a console.info by 'captureConsoleIntegration listens
on warn'. That is no longer true, and a comment stating a false fact is worse
than no comment. The decisions they explain are unchanged.
@abalinda

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Solid telemetry-hygiene change: env gating, console-error-only capture, skip rules and comment corrections are all correct and pinned by tests both ways. One major on the /bridge/exchange-rate 429 skip: it silences the escalation channel for the standing open FX-stampede P2, and the rule's stated justification ("peanut-api already reports the upstream cause") does not hold for that defect — the 429 comes from peanut-api's own global limiter, which nothing reports server-side.

Findings

  • MAJOR · src/utils/sentry.utils.ts:36 · 429 skip rule deletes the escalation signal for the open FX-stampede P2, on a justification that is wrong for the stampede case
    The standing open P2 (same-key FX request stampede, PEANUT-UI-T1M/T1R/T1N/T1P/T1Q, ops/monitor/sentry-triage.md) is detected and escalated purely by the volume of client-captured 429s on /bridge/exchange-rate — the runbook decision ignores the family "at 5 events/60m", i.e. volume is the escalation channel. This skip rule removes it entirely, and also from PostHog (findSkipRule runs in reportNonOkResponse before captureMessage, and posthogErrorMirror mirrors only Sentry events). The comment's justification — "peanut-api already reports the upstream cause" — is accurate for Bridge 5xx but not for the stampede: its 429 is generated by peanut-api's own global @fastify/rate-limit (app.ts, 300/min/IP keyed on CF-Connecting-IP), which produces a plain 429 reply with no onExceeded hook and no error-path Sentry capture, so nothing server-side reports it. Only the Rewards-endpoint spill (429s on endpoints not in the skip table) keeps any signal, and that is indirect and was not the filed fingerprint. Concrete failure: the stampede recurs in production after this ships, the T1M family groups never form again, the "ignore until 5 events/60m" decision can never re-open, and the open P2 loses its only escalation path until someone notices Rewards 429s by chance. Fix: drop this one entry from the skip table until the keyed single-flight fix for no-cache.ts lands (the other two skips have no such twin), or before merge arm an equivalent signal — pin the runbook to escalate on the Rewards-spill 429 groups / an API-side limiter counter and record the watermark in sentry-triage.md and known-noise.md (the docs sweep is already listed as a follow-up; it must cover this decision, not just the invites/perks rows).

  • MAJOR · src/utils/sentry.utils.ts:36 · [claude-opus] New /bridge/exchange-rate 429 skip silences the one alert for a wrong withdrawal minimum; its stated premise is false
    The added rule { pattern: /\/bridge\/exchange-rate/, statuses: [429] } is justified by two claims in its comment, both of which the code contradicts.

(1) "the UI keeps the last rate and retries". It does not. useGetExchangeRate (src/hooks/useGetExchangeRate.tsx:41-53) catches the failure inside queryFn and returns '1'. Because the function resolves rather than throws, react-query treats it as a success — retry: 3 never fires — and '1' is cached for the 5-minute staleTime.

(2) "peanut-api already reports the upstream cause". Not for a 429: the route catches Bridge failures and returns 500 (peanut-api-ts src/routes/bridge/exchange-rates.ts:72-74). A 429 on this path comes from Peanut's own global @fastify/rate-limit (peanut-api-ts src/app.ts:136), which reports no upstream cause at all.

Consequence, on a money screen: bankWithdrawMinUsd computes Math.ceil(localMin / rate) (src/features/withdraw/amount-validation.ts:63) from that rate, and it feeds both the displayed amount-step minimum (src/features/withdraw/useWithdrawRootFlow.ts:165) and the submit-side re-check (src/features/withdraw/useBridgeOfframpFlow.ts:118). With rate = '1' the Mexican minimum becomes ceil(50/1) = $50 instead of ~$3, and GB becomes $3 instead of ~$4 (under-enforced, so Bridge rejects). The isMinReady guard does not catch it — parseFloat('1') > 0 passes, so submission is not gated. This differs from the existing /tokens/price 404 entry the author cites as precedent, whose comment correctly notes it is "a degraded display, never a wrong number"; here it is a wrong number.

Fix: either drop 429 from this rule, or make the fallback honest first — have getExchangeRate/useGetExchangeRate surface the failure (throw so retry applies, or return null so bankWithdrawMinNeedsRate callers keep the submit disabled) and correct the comment to say the 429 originates from our own limiter, not upstream.

Checked clean

  • head SHA verified (git rev-parse HEAD) and diff read in full against base d035b65
  • Env gating: isSentryReportingEnvironment logic traced for production/staging (dev-branch preview)/preview/native/local in sentry-env.ts, sentry-init.ts, sentry.server.config.ts, sentry.edge.config.ts; native client init in instrumentation-client.ts is capacitor-only so 'native' stays reporting; client test exclusion of CAPACITOR_BUILD/PERF_BARE unchanged
  • Console capture narrowed to ['error'] on web/server/edge; native was already ['error'] (instrumentation-client.ts untouched); perks.ts console.error→console.info now safe on both web and native capture sets
  • Skip-rule tests pinned both directions (409/429/401 skipped; 500s still reported, including /manteca/qr-payment/init 500) in sentry.utils.test.ts
  • /invites/validate 409 skip verified against the known-noise row (PEANUT-UI-T5N, benign-PROVEN 2026-09-11) which requested exactly this rule, and validateInviteCode's typedCampaignOnly handling
  • /perks/pending 401 skip matches the pre-existing skip-table row; the call-site leak fix follows the reportNonOkResponse precedent
  • passkeyDebug: captureMessage removal leaves the catch-path captureException and the SignTestTransaction caller intact
  • tracesSampleRate: server 1→0.1 matching the client (sentry-init.ts:61); edge deliberately left at 1 — consistent with the described trade-off
  • Sibling peanut-api-ts read: /bridge/exchange-rate handler and global rate limiter (app.ts rateLimit register) — no onExceeded hook, no error-path capture of limiter 429s, confirming finding 1
  • CI at head: analyze/refs/review/bot-approval green; Deploy-Preview and capture_after suites still in progress at review time
  • Two duplicated sentry.utils.ts files, near-identical server/edge configs and the console.error(non-Error) pattern are pre-existing and explicitly flagged by the author as follow-ups — not counted as findings

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

The usual first reviewer was out of plan, so this review was done by openrouter/z-ai/glm-5.3.

Exact head: c8473e140971 · Context: repo, mono, sibling-repo · Took 3m

Comment thread src/utils/sentry.utils.ts Outdated
Two reviewers landed on the same rule independently, and the code agrees with
them. useGetExchangeRate swallows the failure and returns a rate of '1'
(retry:3 never fires, because resolving is not throwing), bankWithdrawMinUsd
divides by it, and a Mexican bank withdrawal then shows a $50 minimum instead
of about $3 with nothing gating submission. The 429 is the only alert for that
wrong number, and for the open FX-stampede P2 whose runbook entry escalates on
exactly this volume.

The rule's justification was also wrong: the 429 comes from our own global
rate limiter in peanut-api, which reports nothing server-side.

Replaced with a comment saying why it must not be skipped and a test that
fails if anyone adds the rule back. The invites/validate 409 and perks/pending
401 skips stand — neither has a money screen behind it.
@abalinda

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Telemetry-cost hygiene change, reviewed at head f96453b. Both prior findings (the /bridge/exchange-rate 429 skip) are fixed in this head commit: the rule is removed, a comment at the same spot explains why the 429 must keep reporting, and it.each([429, 500]) pins reporting so re-adding the rule fails the suite. The remaining suppressions match the API contract: the only 409 peanut-api returns on /invites/validate is the campaign-only outcome, and both client consumers already treat it as success. No new findings.

Checked clean

  • Prior findings P1/P2: verified at head that the /bridge/exchange-rate 429 skip rule is gone from SKIP_REPORTING (src/utils/sentry.utils.ts), the replacement comment documents the useGetExchangeRate '1'-rate fallback and the FX-stampede escalation, and src/utils/tests/sentry.utils.test.ts pins both 429 and 500 as still reported
  • New /invites/validate 409 skip: checked the sibling peanut-api-ts checkout — the route's only 409 is the campaign-only response (src/routes/invite.ts:445); both client call sites (src/app/actions/invites.ts:22, src/services/invites.ts:41) read it as success via isTypedCampaignOnlyInviteResponse, so no reachable failure is suppressed today. Note: unlike the qr-payment/init 409 rule, this one is not errorCodes-scoped (the campaign-only body carries no error field, so the mechanism cannot scope it) — a future, different 409 on this route would be silently suppressed
  • Environment gating: isSentryReportingEnvironment() composes with the existing inferSentryEnvironment() tagging logic; staging (dev-branch preview) still reports; client/server/edge all gate consistently; the capacitor-preview-reported-as-native gap is acknowledged in the PR body and out of scope
  • Console capture narrowed to ['error'] on client, server and edge; instrumentation-client.ts was already ['error'] — passkeyDebug keeps its captureException for real failures and perks.ts 5xx still reports via fetchWithSentry's captureMessage
  • tracesSampleRate 1 -> 0.1 on server only; client was already 0.1 — no behavior beyond sampling cost
  • CI at this head: no failing checks (refs, bot-approval, analyze, review green; deploy/build suites in progress). Tests not run locally: the detached worktree has no node_modules; the PR's local run claim (8,358 passed) and the new pinned tests rest on CI

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

The usual first reviewer was out of plan, so this review was done by openrouter/z-ai/glm-5.3.

Exact head: f96453b34c22 · Context: repo, peanut-api-ts sibling checkout · Took 3m

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 3 screens moved

5 of 144 shots changed · 139 identical · baseline d035b65 → head 147ef24

worst % screen widths
69.67% early-user 430
43.38% avatar-picker 320, 430
1.50% guest-invite 320, 430

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@abalinda
abalinda marked this pull request as ready for review September 18, 2026 16:36
Copilot AI lite review requested due to automatic review settings September 18, 2026 16:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@abalinda abalinda changed the title chore(sentry): capture console errors only, disable preview builds, skip expected 4xx, move canaries to PostHog chore(sentry): capture console errors only, disable preview builds, skip expected 4xx, drop info-level telemetry Sep 18, 2026

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Re-reviewed at the same head SHA after the P1/P2 fix round. Both prior findings (the /bridge/exchange-rate 429 skip) are fixed in head commit f96453b: the rule is removed, a comment at the same spot in SKIP_REPORTING explains why the 429 must keep reporting, and it.each([429, 500]) pins reporting so re-adding the rule fails the suite. The remaining suppressions match the API contract — the only 409 peanut-api returns on /invites/validate is the campaign-only outcome, which both client consumers already treat as success. No new findings.

Checked clean

  • Prior findings P1/P2: verified at head that the /bridge/exchange-rate 429 skip rule is gone from SKIP_REPORTING (src/utils/sentry.utils.ts), the replacement comment documents the useGetExchangeRate '1'-rate fallback and the FX-stampede escalation, and src/utils/tests/sentry.utils.test.ts pins both 429 and 500 as still reported
  • New /invites/validate 409 skip: checked the sibling peanut-api-ts checkout — the route's only 409 is the campaign-only response (src/routes/invite.ts:445); both client call sites (src/app/actions/invites.ts:22, src/services/invites.ts:41) read it as success via isTypedCampaignOnlyInviteResponse, so no reachable failure is suppressed today. Unlike the qr-payment/init 409 rule this one is not errorCodes-scoped (the campaign-only body carries no error field, so the mechanism cannot scope it) — a future, different 409 on this route would be silently suppressed
  • Environment gating: isSentryReportingEnvironment() composes with the existing inferSentryEnvironment() tagging logic; staging (dev-branch preview) still reports; client/server/edge all gate consistently; the capacitor-preview-reported-as-native gap is acknowledged in the PR body and out of scope
  • Console capture narrowed to ['error'] on client, server and edge; instrumentation-client.ts was already ['error'] — passkeyDebug keeps its captureException for real failures and perks.ts 5xx still reports via fetchWithSentry's captureMessage
  • tracesSampleRate 1 -> 0.1 on server only; client was already 0.1 — no behavior beyond sampling cost
  • CI at this head: no failing checks. Tests not run locally: the detached worktree has no node_modules; the PR's local run claim and the new pinned tests rest on CI

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

The usual first reviewer was out of plan, so this review was done by openrouter/z-ai/glm-5.3.

Exact head: f96453b34c22 · Context: repo, peanut-api-ts sibling checkout · Took 1m

@notion-workspace

Copy link
Copy Markdown

…sage

Two reversions, both to avoid losing a diagnostic somebody depends on.

Preview was going to stop initialising the SDK. It must not: the OTA liveness
proof in ops/native-ota-envless-bundle-rca.md reads preview and canary events
out of Sentry, so a dark preview takes that check with it. Only `development`
is disabled now — which still beats the NODE_ENV guard it replaces, because a
`next build` on a laptop has NODE_ENV=production and reported as production.

passkeyDebug goes back to what `dev` has, captureMessage included. Support
reads the collected navigator/credentials data on registration and
test-transaction failures, and ~300 events a month is a fair price for it.

Error sampling and the console-error-only setting are unchanged and apply
everywhere; tracesSampleRate is a flat 0.1 on client and server.
@abalinda abalinda changed the title chore(sentry): capture console errors only, disable preview builds, skip expected 4xx, drop info-level telemetry chore(sentry): capture console errors only, stop reporting local builds and two expected 4xx Sep 19, 2026
@abalinda

Copy link
Copy Markdown
Contributor Author

/chip review

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

English · Español · Español (Argentina) · Português (Brasil)

Open screen library dashboard

After merge: f3154a9 → 12bd900. Capture complete in all locales.

@abalinda

Copy link
Copy Markdown
Contributor Author

/chip review

Head 147ef2486 is the two coordinator reversions on top of the twice-clean f96453b34 (preview and staging report like production, passkeyDebug.ts byte-identical to dev). Six earlier attempts on this head errored out on the review plan; please review the current head.

@abalinda
abalinda merged commit 12bd900 into dev Sep 19, 2026
32 of 33 checks passed

This branch was successfully deployed

1 active deployment
Preview — 147ef248 Deployed Sep 19, 2026 by vercel[bot]
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