fix(auth): surface meaningful error messages and capture root errors in Sentry#2469
Conversation
API (@packrat/api): - Add @sentry/cloudflare dependency and wrap the Worker fetch/queue handlers with withSentry for per-request scope isolation - New packages/api/src/utils/sentry.ts with captureApiException, apiAddBreadcrumb, setApiUser, clearApiUser helpers - Global onError handler captures all non-validation, non-404 errors with method/path/error_code tags; queue handler wraps processQueueBatch and handleEmbeddingsBatch with capture + rethrow - Auth middleware sets Sentry user context (id, email, role) after every successful session resolve; breadcrumbs on unauthenticated / forbidden rejections; captureException on getSession failures - weather routes: captureApiException in every catch block with operation name, userId, weather_operation tag, and relevant extras - chat route: captures missing AI provider config; breadcrumb before stream starts with contextType/packId/messageCount; captures streamText onError with provider + context tags - weatherService: captures HTTP error responses with http_status tag Expo (apps/expo): - _layout.tsx: add tracesSampleRate 0.2, sendDefaultPii false, environment tag, beforeBreadcrumb filter to strip /api/auth/ URLs; setUser with id/email/username on startup - ErrorBoundary: withScope in onError adds error_source tag and componentStack + errorName/errorMessage as Sentry extras before capture - useAuthActions: Sentry.addBreadcrumb for every auth flow step (sign-in attempt/success, Google/Apple/email, sign-up, sign-out, password reset, email verify); captureException in every catch with auth_method and auth_action tags; setUser after successful sign-in; setUser(null) on sign-out and account deletion - useAuthInit: setUser after background session refresh; breadcrumbs for session expiry and definitive auth failures; setUser(null) on clear; captureException for startup auth failures https://claude.ai/code/session_01HCrFacYwgoJ4xLTqu21gWb
…in Sentry Previously, Better Auth client error responses were wrapped in `new Error(error.message ?? fallback)` before being thrown and/or passed to Sentry. This had two problems: 1. Sentry received a synthetic error with no status code, error code, or original context. 2. Users saw either the server's raw message or a generic fallback with no actionable detail. Introduce `AuthClientError` and `toAuthError` (features/auth/lib/authErrors.ts) which: - Converts a Better Auth error object into a proper JS Error carrying `status` and `code` - Maps known Better Auth error codes (USER_ALREADY_EXISTS, INVALID_EMAIL_OR_PASSWORD, etc.) to clear user-facing copy - Falls back to a generic "something went wrong" for 5xx responses instead of leaking internal messages Update `useAuthActions` throughout: - Replace every `throw new Error(error.message ?? fallback)` with `toAuthError` - Fix the double-`new Error` antipattern in forgotPassword/resetPassword/verifyEmail/resendVerificationEmail where two separate error objects were created (one captured, one thrown) - Thread `httpStatus` and `errorCode` through every `captureException` extra so Sentry has the original HTTP context
|
Warning Review limit reached
Your plan currently allows 1 review/hour. Refill in 33 minutes and 13 seconds. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more review capacity refills, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (18)
WalkthroughThis PR comprehensively integrates Sentry monitoring across the PackRat client and API. It introduces centralized error utilities, standardizes auth error handling, and instruments routes/services with structured exception reporting, breadcrumbs, and user context tracking. ChangesSentry Monitoring Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…structions Add a "Monitoring (Sentry)" section to CLAUDE.md and copilot-instructions.md covering: - Use addBreadcrumb before async operations - captureException in every catch block with tags and extra context - Never re-wrap the root error in new Error() before capturing — loses stack/status/code - Better Auth errors: use toAuthError from authErrors.ts to produce a single AuthClientError (one object to capture and throw, not two separate synthetic errors) - Include httpStatus and errorCode in extra for all HTTP errors - API side: use captureApiException from @packrat/api/utils/sentry Also adds a §10 "Sentry Instrumentation" check to the kieran-typescript-reviewer agent with concrete pass/fail examples so code reviews catch missing or broken capture patterns.
There was a problem hiding this comment.
Pull request overview
This PR improves Sentry observability and user-facing auth errors by preserving root error context (status/code) and adding consistent instrumentation across the Expo app and the Cloudflare Workers API.
Changes:
- Added an Expo
AuthClientError+toAuthErrormapper to surface user-friendly auth messages while keeping HTTP status/error codes for Sentry context. - Introduced API-side Sentry helpers and initialized Sentry in the Worker entrypoint; added structured
captureApiExceptionusage across middleware/routes/services. - Updated Sentry initialization/user identification and added breadcrumbs/captures across key auth flows and error boundaries; documented the new monitoring conventions.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/api/src/utils/sentry.ts | New API Sentry helper utilities (captureApiException, breadcrumbs, user helpers). |
| packages/api/src/services/weatherService.ts | Captures and rethrows structured weather API failures. |
| packages/api/src/routes/weather.ts | Adds Sentry capture in weather endpoints and schema-validation error reporting. |
| packages/api/src/routes/chat.ts | Adds breadcrumbs and structured captures around AI chat streaming errors. |
| packages/api/src/middleware/auth.ts | Captures Better Auth session retrieval failures; sets Sentry user context per request. |
| packages/api/src/index.ts | Wraps Worker handler with withSentry; captures unexpected Elysia errors and queue handler failures. |
| packages/api/package.json | Adds @sentry/cloudflare dependency. |
| apps/expo/features/auth/lib/authErrors.ts | New Better Auth error → user-friendly message mapping (AuthClientError, toAuthError). |
| apps/expo/features/auth/hooks/useAuthInit.ts | Syncs Sentry user identity with session lifecycle and captures init failures. |
| apps/expo/features/auth/hooks/useAuthActions.ts | Uses toAuthError, adds breadcrumbs, and captures auth flow failures with status/code extras. |
| apps/expo/components/initial/ErrorBoundary.tsx | Adds Sentry capture and componentStack context in the app error boundary. |
| apps/expo/app/_layout.tsx | Updates Sentry init config, breadcrumb filtering, and sets initial Sentry user identity. |
| copilot-instructions.md | Documents required Sentry instrumentation patterns for Expo and API code. |
| CLAUDE.md | Documents required Sentry instrumentation patterns for Expo and API code. |
| .github/agents/kieran-typescript-reviewer.agent.md | Adds Sentry instrumentation guidance to the reviewer agent instructions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .onError(({ error, code, request }) => { | ||
| console.error('Error occurred:', error); | ||
|
|
||
| // Only report unexpected server errors — not user-input or routing errors. | ||
| if (code !== 'VALIDATION' && code !== 'PARSE' && code !== 'NOT_FOUND') { | ||
| captureApiException(error, { |
|
|
||
| withScope((scope) => { | ||
| scope.setTag('operation', operation); | ||
| if (userId) scope.setUser({ id: userId }); |
| tags: { weather_operation: 'forecast', error_type: 'schema_validation' }, | ||
| extra: { locationId: id, invalidPaths }, | ||
| }); | ||
| throw new Error(`Weather forecast response failed schema validation at: ${invalidPaths}`); |
| // Attach the component stack as extra context so Sentry shows exactly | ||
| // which component tree caused the crash. | ||
| Sentry.withScope((scope) => { | ||
| scope.setTag('error_source', 'error_boundary'); | ||
| scope.setExtra('componentStack', info.componentStack); | ||
| if (error instanceof Error) { | ||
| scope.setExtra('errorName', error.name); | ||
| scope.setExtra('errorMessage', error.message); | ||
| } | ||
| Sentry.captureException(error); | ||
| }); |
| // Sync the authenticated user to Sentry on startup so every event is | ||
| // associated with the correct user identity. | ||
| const user = userStore.peek(); | ||
| if (user) { | ||
| Sentry.setUser(user); | ||
| Sentry.setUser({ | ||
| id: user.id, | ||
| email: user.email, | ||
| username: `${user.firstName} ${user.lastName}`.trim(), | ||
| }); |
| Sentry.addBreadcrumb({ | ||
| category: 'auth', | ||
| message: 'Email sign in attempt', | ||
| level: 'info', | ||
| data: { email }, | ||
| }); |
| TOO_MANY_REQUESTS: 'Too many attempts. Please wait a moment and try again.', | ||
| INVALID_TOKEN: 'This link has expired or is invalid. Please request a new one.', | ||
| EXPIRED_TOKEN: 'This link has expired. Please request a new one.', | ||
| PASSWORD_TOO_SHORT: 'Password is too short.', |
| import { captureApiException } from '@packrat/api/utils/sentry'; | ||
| import { withSentry } from '@sentry/cloudflare'; | ||
| import { Elysia } from 'elysia'; |
…obal handler, align route catches - localModelManager: capture download failures (HTTP status + network) and _prepareLlamaModel errors with device OS/version context; add breadcrumbs before each operation so the event trail is visible in Sentry - _layout.tsx + eas.json + app.config.ts: read APP_VARIANT from Constants.expoConfig.extra instead of NODE_ENV so development/preview EAS builds no longer report as environment:'production' in Sentry - TanstackProvider: wire QueryCache + MutationCache global onError handlers with 401/429/404 noise filter; every future hook gets coverage for free - trails, trailConditions, wildlife, packs, user: add captureApiException to catch blocks that were swallowing errors before the global handler; user routes capture then rethrow for double-context; trails skips intentional 503
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/expo/features/auth/hooks/useAuthActions.ts (1)
111-165:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winGoogle sign-in leaves loading state stuck on successful path.
setIsLoading(true)on Line 112 is never reverted when Google auth succeeds, so UI can remain blocked.Suggested fix
const signInWithGoogle = async () => { setIsLoading(true); Sentry.addBreadcrumb({ category: 'auth', message: 'Google sign in attempt', level: 'info' }); try { @@ } catch (error) { - setIsLoading(false); - if (isErrorWithCode(error) && error.code === statusCodes.SIGN_IN_CANCELLED) { @@ } throw error; + } finally { + setIsLoading(false); } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/expo/features/auth/hooks/useAuthActions.ts` around lines 111 - 165, The signInWithGoogle function sets setIsLoading(true) but never resets it on success, leaving the UI stuck; update signInWithGoogle to always call setIsLoading(false) after a successful sign-in (e.g., immediately after applySession(...) and the Sentry breadcrumb) or, better, move the loading reset into a finally block so setIsLoading(false) runs on both success and error; look for the signInWithGoogle function and the applySession call to place the fix.apps/expo/eas.json (1)
23-40:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix Sentry environment tagging for EAS
e2ebuilds
apps/expo/eas.json’sbuild.e2esets"environment": "preview"and"channel": "preview"but doesn’t setenv.APP_VARIANT.apps/expo/app.config.tsthen defaultsextra.appVariantto'production', andapps/expo/app/_layout.tsxuses that value for Sentryenvironment, soe2eevents are tagged as production. Addenv: { "APP_VARIANT": "preview" }to thee2eprofile ife2eshould be tagged as preview in Sentry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/expo/eas.json` around lines 23 - 40, The e2e profile in eas.json is missing APP_VARIANT so app.config.ts falls back to 'production' and Sentry tags e2e builds as production; add an env entry to the e2e profile: set APP_VARIANT to "preview" (i.e. add env: { "APP_VARIANT": "preview" } under the "e2e" profile) so extra.appVariant in app.config.ts and the Sentry environment used in app/_layout.tsx will be "preview".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/agents/kieran-typescript-reviewer.agent.md:
- Around line 119-134: Add missing blank lines around the fenced TypeScript code
blocks to satisfy MD031: ensure there is a blank line before each opening ```ts
fence and a blank line after each closing ``` fence in the markdown containing
the examples that call Sentry.captureException and toAuthError; verify each
fenced block starts with ```ts on its own line and ends with ``` on its own line
and that there is an empty line separating the preceding and following
paragraphs (the blocks showing the failing snippet using new Error(...) and the
passing snippet using const err = toAuthError(...)).
In `@apps/expo/app/_layout.tsx`:
- Around line 35-42: The beforeBreadcrumb filter currently drops breadcrumbs for
'/api/auth/' to avoid token leakage; instead, update
beforeBreadcrumb(breadcrumb) to scrub sensitive query parameters for all HTTP
breadcrumbs: when breadcrumb.type === 'http' and breadcrumb.data?.url is
present, parse the URL (using URL or a safe parser), remove or redact query keys
in a denylist (e.g., token, access_token, auth, password, jwt, session), rebuild
the URL without those params and assign it back to breadcrumb.data.url (or
replace values with '[REDACTED]'), and then return breadcrumb; keep the existing
special-case behavior (if you still want to drop some endpoints) but prefer
sanitization over dropping so other breadcrumbs are preserved.
- Around line 45-54: The module-level call that captures userStore.peek() and
immediately calls Sentry.setUser should be removed and replaced with a useEffect
that waits for userSyncState.isPersistLoaded (or simply rely on the existing
useAuthInit/applySessionUser path); specifically, stop using userStore.peek() at
module evaluation in apps/expo/app/_layout.tsx and instead in your component
useEffect watch userSyncState.isPersistLoaded and then call Sentry.setUser with
the hydrated user (id, email, username) — or remove the module-scope
Sentry.setUser entirely so applySessionUser (invoked by useAuthInit) is the
single place that sets the Sentry user.
In `@apps/expo/components/initial/ErrorBoundary.tsx`:
- Around line 56-66: The code is adding redundant extras for errorName and
errorMessage inside Sentry.withScope; remove the two scope.setExtra calls that
set 'errorName' and 'errorMessage' and keep the component stack and tag logic
intact so Sentry.captureException(error) still receives the Error instance
(refer to Sentry.withScope, scope.setExtra('componentStack',
info.componentStack), the two scope.setExtra('errorName'...) /
scope.setExtra('errorMessage'...) calls to remove, and
Sentry.captureException(error) to leave in place).
In `@apps/expo/features/auth/hooks/useAuthActions.ts`:
- Around line 85-90: Sentry.addBreadcrumb calls in useAuthActions.ts are sending
plaintext emails (PII); change those to send a non-identifying representation
instead (e.g., emailDomain or a hashed/redacted value) by creating a small
helper (e.g., redactEmail or hashEmail) and use it wherever Sentry.addBreadcrumb
or Sentry.setExtra is called in this file; update the breadcrumb data from {
email } to something like { emailDomain: redactEmail(email) } or { emailHash:
hashEmail(email) } and apply the same pattern to all other auth
breadcrumbs/extras in useAuthActions.ts so no raw email is sent to telemetry.
- Around line 261-263: In the catch block inside useAuthActions.ts where
Sentry.captureException(error, { tags: { auth_action: 'sign_out' } }) is called,
augment the Sentry call to include HTTP context: detect if error is an HTTP
error (e.g., has response/status or errorCode), extract httpStatus and
errorCode, and pass them in the Sentry payload under extra (e.g., extra: {
httpStatus, errorCode }) while keeping the existing tags; update the
Sentry.captureException invocation in the sign-out error handler to include
these fields so HTTP failures are searchable.
In `@apps/expo/providers/TanstackProvider.tsx`:
- Around line 7-10: Refactor the current shouldCapture function so callers can
access the extracted HTTP metadata: either change shouldCapture(error) to return
an object like {capture: boolean, httpStatus?: number, errorCode?: string} or
move the status/errorCode extraction into the Sentry handlers and use the same
filter logic there; ensure the symbols mentioned (shouldCapture and the Sentry
capture handlers where Sentry.captureException is called) include httpStatus and
errorCode in the Sentry extra payload for HTTP errors and preserve the existing
filter behavior (do not capture when status is 401, 429, or 404).
- Around line 15-21: The Sentry capture in the onError handler should include
HTTP metadata: extract the HTTP status from error.status or error.httpStatus and
the error code from error.code or error.errorCode (fall back to undefined if
missing) and add them to the Sentry.extra object alongside query.queryKey;
update the onError function (and keep shouldCapture logic) so
Sentry.captureException(error, { tags: {...}, extra: { queryKey: query.queryKey,
httpStatus, errorCode } }) is called with those extracted fields.
- Around line 24-28: The Sentry call in the onError handler should include
httpStatus and errorCode in the extra payload for HTTP errors; update the
onError implementation (the shouldCapture check and Sentry.captureException
call) to extract httpStatus (e.g., from error.response?.status or error.status)
and errorCode (e.g., from error.code or error.errorCode) and pass them inside
the extra object when calling Sentry.captureException alongside the existing
tags so HTTP errors become searchable.
In `@packages/api/src/index.ts`:
- Around line 55-63: The Sentry payload sent by captureApiException in the
elysia.onError path is missing httpStatus in the extra metadata; update the call
to captureApiException to include httpStatus (use the existing code variable
that represents the HTTP status) inside the extra object so extra contains both
errorCode and httpStatus, ensuring searchable HTTP error metadata.
In `@packages/api/src/middleware/auth.ts`:
- Around line 31-35: The Sentry capture calls in the auth middleware (e.g., the
captureApiException call around operation 'auth.getSession' and the similar
catch at lines ~77-82) must include extra fields for httpStatus and errorCode so
outages are searchable; update the captureApiException invocations (for
operation 'auth.getSession' and the other auth operation) to pass an extra
object like extra: { httpStatus: 500, errorCode: '<unique_error_code>' } (choose
a clear code such as 'auth.getSession' or 'auth.<operationName>') alongside
existing operation/tags so the 500 captures include both httpStatus and
errorCode.
In `@packages/api/src/routes/chat.ts`:
- Around line 97-102: The Sentry capture in the chat.stream branch is missing
HTTP metadata; update the captureApiException call (the one creating new
Error('AI provider not configured')) to include extra.httpStatus: 500 and
extra.errorCode: something descriptive (e.g., 'AI_PROVIDER_NOT_CONFIGURED'),
keeping the existing operation: 'chat.stream', userId and tags: { ai_provider:
AI_PROVIDER } so the error becomes searchable by HTTP status and code.
In `@packages/api/src/routes/packs/index.ts`:
- Around line 208-211: Several captureApiException calls in this file (e.g., the
one with operation 'packs.analyzeImage' and the other 500-path catches) are
missing required httpStatus and errorCode fields in the extra payload; update
every captureApiException(...) used in these catch blocks to include httpStatus
(e.g., error?.status || 500) and errorCode (e.g., error?.code ||
'internal_server_error') in the second-argument object alongside operation and
tags so Sentry captures both values for searchability; locate all calls to
captureApiException in this file (including the ones mentioned around the other
catches) and add these two keys to the extra payload.
- Around line 212-217: Replace the response that includes error.message with a
generic 500 message: in the packs route where you call status(500, { error:
error instanceof Error ? `Failed to analyze image: ${error.message}` : 'Failed
to analyze image' }), remove echoing error.message and always return a
non-sensitive string like 'Failed to analyze image'; continue to capture the
original error with your existing Sentry/telemetry call (or add
Sentry.captureException(error) nearby) so internals stay logged but not leaked
to clients.
In `@packages/api/src/routes/trailConditions/reports.ts`:
- Around line 65-69: The Sentry captures via captureApiException currently lack
searchable HTTP metadata; update each captureApiException call in this file
(e.g., the one with operation 'trailConditions.list' and the other occurrences
around the ranges noted) to add extra.httpStatus: 500 and extra.errorCode:
'INTERNAL_SERVER_ERROR' (or the appropriate error code used by the project) so
that the extra object includes { trailName, limit, httpStatus: 500, errorCode:
'INTERNAL_SERVER_ERROR' }; apply the same change to the other
captureApiException calls referenced (around lines 130-134, 171-175, 230-234,
271-275) ensuring the extras are consistently named for searchability.
In `@packages/api/src/routes/trails/index.ts`:
- Around line 93-97: The Sentry captures calling captureApiException in the
trails route need to include searchable fields; update the captureApiException
calls (e.g., the one with operation 'trails.search' and the similar captures
later) to add httpStatus and a route-specific errorCode into the extra object
(for example extra: { q, lat, lon, radius, sport, httpStatus: 500, errorCode:
'TRAILS_SEARCH_ERROR' }); ensure each distinct catch uses an appropriate
httpStatus and a unique errorCode (e.g., TRAILS_SEARCH_ERROR,
TRAILS_LOOKUP_ERROR, TRAILS_CREATE_ERROR) when modifying the extra payload
passed to captureApiException.
In `@packages/api/src/routes/weather.ts`:
- Around line 48-53: The Sentry captures using captureApiException in the
weather route handlers (e.g., the block with operation 'weather.search' that
logs user?.userId and extra: { query: q }) must include httpStatus and errorCode
in the extra payload; update the extra object to add httpStatus (the HTTP status
returned by WeatherAPI or your response) and errorCode (the route-level error
code constant you define/use for weather errors). Apply the same change to the
other captureApiException calls in this file that handle HTTP errors (the
similar blocks around the other weather handlers) so every HTTP error capture
includes extra: { query, httpStatus, errorCode } (and keep existing tags/userId
fields).
In `@packages/api/src/services/weatherService.ts`:
- Around line 37-41: The Sentry capture call in
weatherService.getWeatherForLocation currently puts the HTTP status in tags;
change it so captureApiException includes httpStatus and errorCode inside the
extra object (e.g., extra: { location, apiMessage, httpStatus:
String(response.status), errorCode: 'OPENWEATHERMAP_HTTP_ERROR' }) and remove
the http status from tags; update the captureApiException invocation accordingly
to ensure httpStatus and errorCode are present and searchable.
---
Outside diff comments:
In `@apps/expo/eas.json`:
- Around line 23-40: The e2e profile in eas.json is missing APP_VARIANT so
app.config.ts falls back to 'production' and Sentry tags e2e builds as
production; add an env entry to the e2e profile: set APP_VARIANT to "preview"
(i.e. add env: { "APP_VARIANT": "preview" } under the "e2e" profile) so
extra.appVariant in app.config.ts and the Sentry environment used in
app/_layout.tsx will be "preview".
In `@apps/expo/features/auth/hooks/useAuthActions.ts`:
- Around line 111-165: The signInWithGoogle function sets setIsLoading(true) but
never resets it on success, leaving the UI stuck; update signInWithGoogle to
always call setIsLoading(false) after a successful sign-in (e.g., immediately
after applySession(...) and the Sentry breadcrumb) or, better, move the loading
reset into a finally block so setIsLoading(false) runs on both success and
error; look for the signInWithGoogle function and the applySession call to place
the fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d39b8eb8-eb1f-4ef7-9bf6-d13e148644c2
📒 Files selected for processing (25)
.github/agents/kieran-typescript-reviewer.agent.mdCLAUDE.mdapps/expo/app.config.tsapps/expo/app/_layout.tsxapps/expo/components/initial/ErrorBoundary.tsxapps/expo/eas.jsonapps/expo/features/ai/lib/localModelManager.tsapps/expo/features/auth/hooks/useAuthActions.tsapps/expo/features/auth/hooks/useAuthInit.tsapps/expo/features/auth/lib/authErrors.tsapps/expo/providers/TanstackProvider.tsxcopilot-instructions.mdpackages/api/package.jsonpackages/api/src/index.tspackages/api/src/middleware/auth.tspackages/api/src/routes/chat.tspackages/api/src/routes/packs/index.tspackages/api/src/routes/trailConditions/reports.tspackages/api/src/routes/trails/index.tspackages/api/src/routes/user/index.tspackages/api/src/routes/weather.tspackages/api/src/routes/wildlife/index.tspackages/api/src/services/weatherService.tspackages/api/src/utils/env-validation.tspackages/api/src/utils/sentry.ts
| scope.setTag('operation', operation); | ||
| if (userId) scope.setUser({ id: userId }); | ||
| if (tags) { |
| .onError(({ error, code, request }) => { | ||
| console.error('Error occurred:', error); | ||
|
|
||
| // Only report unexpected server errors — not user-input or routing errors. | ||
| if (code !== 'VALIDATION' && code !== 'PARSE' && code !== 'NOT_FOUND') { | ||
| captureApiException(error, { | ||
| operation: 'elysia.onError', | ||
| tags: { | ||
| error_code: code, | ||
| method: request?.method ?? 'UNKNOWN', | ||
| path: request ? new URL(request.url).pathname : 'UNKNOWN', | ||
| }, | ||
| extra: { errorCode: code }, | ||
| }); | ||
| } | ||
|
|
||
| if (code === 'VALIDATION' || code === 'PARSE') { |
| userId: user?.userId, | ||
| tags: { weather_operation: 'forecast', error_type: 'schema_validation' }, | ||
| extra: { locationId: id, invalidPaths }, | ||
| }); | ||
| throw new Error(`Weather forecast response failed schema validation at: ${invalidPaths}`); |
| // Attach the component stack as extra context so Sentry shows exactly | ||
| // which component tree caused the crash. | ||
| Sentry.withScope((scope) => { | ||
| scope.setTag('error_source', 'error_boundary'); | ||
| scope.setExtra('componentStack', info.componentStack); | ||
| if (error instanceof Error) { | ||
| scope.setExtra('errorName', error.name); | ||
| scope.setExtra('errorMessage', error.message); | ||
| } | ||
| Sentry.captureException(error); | ||
| }); |
| Sentry.addBreadcrumb({ | ||
| category: 'auth', | ||
| message: 'Email sign in attempt', | ||
| level: 'info', | ||
| data: { email }, | ||
| }); |
- signInWithGoogle: move setIsLoading(false) to finally block so loading state always resets, even on successful sign-in - Redact raw email from Sentry breadcrumbs and extra (use emailDomain) to reduce PII exposure while keeping auth flow context - Add httpStatus/errorCode to sign-out, query/mutation cache, auth middleware, chat, packs, trailConditions, trails, weather Sentry captures so HTTP errors are searchable in Sentry - authErrors: align EXPIRED_TOKEN message with INVALID_TOKEN for consistent security-neutral copy - eas.json: add APP_VARIANT=preview to e2e profile so Sentry tags e2e builds as preview instead of falling back to production - _layout.tsx: replace auth-URL breadcrumb drop with URL param scrubbing for all HTTP breadcrumbs; remove module-level Sentry.setUser race (rely on applySession in useAuthActions) - ErrorBoundary: use beforeCapture to set error_source tag instead of manually calling captureException inside onError (avoids duplicate events); drop redundant errorName/errorMessage extras - sentry.ts: tag userId instead of calling setUser to avoid overwriting richer user context already set by setApiUser - index.ts: remove duplicate console.error (captureApiException already logs); coerce Elysia error code to string to fix TS2322 - weather.ts: return 500 response after capturing ZodError instead of re-throwing a synthetic Error (prevents double-capture by global onError) - weatherService.ts: move http_status from tags to extra as httpStatus, add errorCode for searchability
Conflict in localModelManager.ts: kept development's console.log and if (!_isCancellingDownload) guard, combined with our Sentry capture so download errors are reported to Sentry only when not user-cancelled.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
packrat-admin | 9011856 | Commit Preview URL Branch Preview URL |
May 24 2026, 02:24 PM |
Coverage Report for API Unit Tests Coverage (./packages/api)
File Coverage
|
||||||||||||||||||||||||||||||||||||||
Coverage Report for Expo Unit Tests Coverage (./apps/expo)
File CoverageNo changed files found. |
Deploying packrat-landing with
|
| Latest commit: |
9011856
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://70aac2be.packrat-landing.pages.dev |
| Branch Preview URL: | https://fix-auth-error-quality.packrat-landing.pages.dev |
Problem
Better Auth client errors were wrapped in
new Error(error.message ?? fallback)before being thrown and captured. This caused two issues:Sentry got synthetic errors: A brand-new
Errorwas created with just the message string, losing the HTTP status code, Better Auth error code, and any original stack context. InforgotPassword/resetPassword/verifyEmail/resendVerificationEmail, two separatenew Error()objects were even created — one captured, one thrown.Users saw unhelpful messages: On a 500, they'd see
"Sign up failed"or the server's raw internal message. OnUSER_ALREADY_EXISTS, they'd see whatever the server returned rather than clear, actionable copy.Solution
features/auth/lib/authErrors.ts(new)Introduces
AuthClientError(extendsError, carriesstatusandcode) andtoAuthError(source, fallback):USER_ALREADY_EXISTS→ "An account with this email already exists. Try signing in instead."INVALID_EMAIL_OR_PASSWORD/INVALID_PASSWORD/USER_NOT_FOUND→ "Invalid email or password." (security-neutral)INVALID_TOKEN/EXPIRED_TOKEN→ "This link has expired or is invalid. Please request a new one."TOO_MANY_REQUESTS→ "Too many attempts. Please wait a moment and try again."features/auth/hooks/useAuthActions.tsthrow new Error(error.message ?? fallback)replaced withthrow toAuthError(error, fallback)new Errorantipattern:forgotPassword,resetPassword,verifyEmail,resendVerificationEmailnow create a singleAuthClientError, capture it, and throw itcaptureExceptioncall now includeshttpStatusanderrorCodeinextraso Sentry has the full original HTTP contextTest plan
httpStatusanderrorCodetags in the extras, and theAuthClientErrorname appears in the issue titleSummary by CodeRabbit
Release Notes