fix(observability): the four production Sentry findings of 2026-09-03, each at its root - #1384
fix(observability): the four production Sentry findings of 2026-09-03, each at its root#1384teetangh wants to merge 7 commits into
Conversation
These five were already prettier-dirty on dev; formatting them first keeps the fix commits reviewable as behaviour changes only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
…on error (FAMILIARISE_WEB-1B) The SDK validates the RESPONSE against its own generated schema and throws before handing back the status. Its 422 schema requires an `errors` record, but both 422s Novu documents for this endpoint — an unknown or unpublished workflow, and an idempotency key reused with a different body — answer with `statusCode` and `message` only. So a rejected trigger reached Sentry as a ZodError about a field of the SDK's own error envelope, and neither the status nor Novu's reason survived. No published @novu/api relaxes that field (checked through 3.19.1), so the wrapper reads the status off the thrown error instead of chasing a bump. A 2xx the SDK merely failed to parse counts as an accepted send, because the notification is already queued in that case; anything else reports its real status, Novu's own message and the workflow id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
…MILIARISE_WEB-1C) Both write actions on the consultee appointments page read the body before checking the response, so a Netlify crash page, a function timeout or a followed redirect to sign-in became `SyntaxError: Unexpected token '<'` — in the toast the consultee read and in the error feed. `res.ok` alone never caught the redirect, which arrives as HTML at status 200. `requireJsonResponse` checks the status and the content type before parsing and throws the existing typed `ApiResponseError`, which carries the status. `parseJsonResponse` now delegates to it, the dashboard query factory uses it (its old message ended in an empty `statusText` over HTTP/2), and the cancel and reschedule actions go through it — so the cancel dialog still recognises the 409 that means the booking changed state underneath it, and Sentry gets a warning naming the status instead of a parse error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
…FAMILIARISE_WEB-1D) `TypeError: Failed to fetch` carries no status at all — it means the request never got an answer. React Query already retries this sync and nothing user-visible depends on it, so reporting it at error level paged someone for a dropped connection. Only a real HTTP answer reports now: a 4xx as a modelled outcome, a 5xx as a fault, both at warning with the status attached. The hook also skips the call while the browser reports itself offline. React Query's `networkMode: "online"` does not cover this on its own — its `onlineManager` starts at `online = true` and only ever flips on the window connectivity EVENTS, so a tab opened while already offline sails past that guard and fires the request anyway. Reading `navigator.onLine` through `useSyncExternalStore` closes that opening without latching `enabled` off. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
…RISE_WEB-10) The token action refuses to mint without a session, and a dashboard left open past its cookie's expiry called it regardless — 8 unauthorized throws on the error feed with nobody to show them to. The connector now consults its own session and skips the mint once the session is known to be absent. A session that is still loading counts as permitted on purpose: blocking the first mint on that round trip would put a serial wait back on the join path, which is what the prefetch effect exists to remove (#248). The video SDK's later `tokenProvider` refreshes read the answer through a ref, so `getCachedToken` keeps its identity and the connect effect does not re-fire. The server-side refusal stays as the backstop, now marked expected. An error that escapes a server action is captured by Next's `onRequestError` hook, which takes no per-call options, so the marker rides on the thrown error and `beforeSend` stamps the tag — the only way to reach that capture path. It re-levels an event and never drops one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7
✅ Deploy Preview for familiarise ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reachedNext included review available in 53 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 64 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (11)
Comment |
|




What
Wave-6 PR F (umbrella #1319): the four production Sentry issues collected on 2026-09-03, each fixed at its root in its own commit so a revert is surgical. The Prisma connection-timeout issue (FAMILIARISE_WEB-9) is the instance-boot class tracked under #1124 and is deliberately untouched.
ResponseValidationErroron request-for-approval. Replaying the event's exact Zod error against every matcher in the installed SDK shows it is the 422 schema, whoseerrorsfield the API does not send for this endpoint's two documented 422 causes (workflow_not_found, or an idempotency key reused with a different body). Novu rejected the trigger; the SDK then failed to parse the rejection. No SDK version fixes the schema (checked up to 3.19.1), so the wrapper inlib/novu/service.tsnow reads the status off the thrown error, reports a 2xx as accepted, and otherwise reports the real status, Novu's message and the workflow id. Both trigger sites are wired.response.json()before checkingresponse.ok. A sharedrequireJsonResponseinlib/fetch-helpers.tschecks the status and the content type before parsing and throws the existing typedApiResponseError(also for a followed sign-in redirect that comes back 200 with HTML). The dashboard query helper and both actions use it; the cancel dialog's 409 branch keys off the status.Failed to fetchin the Novu subscriber sync. The server route only ever answers JSON, so this is transport-level. The hook no longer reports a fault with no HTTP status (React Query retries it), reports a real answer at warning, and reads connectivity throughuseSyncExternalStorebecause the installed query core initialises online to true and never readsnavigator.onLine.StreamProviderImplgates the mint on the client's own session (a pending session still counts, so the first mint keeps its place off the join path per 🐌 Performance: Stream Chat sync runs on every dashboard load causing excessive API calls #248); the server-side throw stays as the backstop and now carries an expected-error marker thatbeforeSendre-levels without dropping.Verification
Cold
tscclean; 202 suites / 2,628 tests green; eslint zero warnings on all ten touched files; prettier clean. One pin file,__tests__/lib/require-json-response.test.ts(HTML at 502, HTML at 200, a 409 envelope, the happy path). Commit 1 is format-only for five files that were prettier-dirty ondev.Owner action surfaced
The 422 shape points at
new-booking-requestnot being published in the production Novu environment; there is no workflow-sync script in this repo, so the ids inlib/novu/workflows.tsare hand-created in the dashboard and can drift silently. This PR makes the next occurrence say so in one line; publishing the workflow is a dashboard task, alongsideappointment-partially-scheduled.Sentry issue status is unchanged by this PR; the issues are resolved against the release that carries it, then re-checked by reproduction.
Part of #1319.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MgadJydWEKkdhhzY58yiL7