Fetch, analyze, and resolve Sentry errors with CLI integration; use when reviewing production errors, resolving issues, analyzing error patterns, or managing Sentry configuration
Every Sentry issue that is addressed or fixed MUST be documented with a comment on Sentry itself. This is non-negotiable.
For EVERY Sentry issue you fix, you MUST complete ALL of these steps:
# REQUIRED after every fix - DO NOT SKIP
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment <issue_id> "š§ FIX DEPLOYED - $(date +%Y-%m-%d)
Commit: <hash>
File: <path>
Change: <description>
Monitoring for recurrence."
A fix is NOT complete until:
If you fix code but forget to comment on Sentry, the task is NOT done. Go back and add the comment before proceeding.
Use this skill when:
Location: .claude/skills/sentry-error-manager/scripts/sentry.sh
This is the only tool you need for all Sentry operations. It auto-loads .env.local for authentication.
# View all unresolved issues
./.claude/skills/sentry-error-manager/scripts/sentry.sh list
# Get details for specific issue
./.claude/skills/sentry-error-manager/scripts/sentry.sh get 7034568465 --events
# Add comment to track progress
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment 7034568465 "š INVESTIGATING"
# Resolve after 3+ days zero recurrence
./.claude/skills/sentry-error-manager/scripts/sentry.sh resolve 7034568465
| Category | Command | Description |
|---|---|---|
| View | list |
List unresolved issues |
list --since 7 |
Issues from last 7 days | |
get <id> --events |
Issue details with events | |
search <query> |
Search by keyword | |
stats |
Statistics overview | |
| Manage | resolve <id...> |
Mark as resolved |
unresolve <id...> |
Reopen issues | |
ignore <id...> |
Ignore/mute issues | |
assign <id> |
Assign to Claude Code (3990106) | |
unassign <id> |
Remove assignment | |
| Comments | comment <id> <text> |
Add comment |
comments <id> |
List all comments | |
edit-comment <id> <cmt-id> <text> |
Edit comment | |
delete-comment <id> <cmt-id> |
Delete comment |
Environment: Token is loaded automatically from .env.local:
SENTRY_AUTH_TOKEN=sntryu_...
Get Token: https://sentry.io/settings/account/api/auth-tokens/
Required Scopes: event:read, event:write, event:admin, project:read, project:write
Is fix deployed to production?
āā NO ā Wait for deployment
āā YES ā Has it been 3+ days since last event?
āā NO ā Wait longer
āā YES ā ā
Safe to resolve
# Start investigation
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment <id> "š INVESTIGATING - $(date +%Y-%m-%d)
Assigned to: Claude Code
Status: Analyzing root cause"
# Fix deployed
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment <id> "š§ FIX DEPLOYED - $(date +%Y-%m-%d)
Commit: <hash>
File: <path>
Change: <description>
Monitoring for recurrence."
# Resolve
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment <id> "ā
RESOLVED - $(date +%Y-%m-%d)
Reason: 3+ days zero recurrence
Resolved by: Claude Code"
# 1. List current issues
./.claude/skills/sentry-error-manager/scripts/sentry.sh list
# 2. Check each issue's last seen date
./.claude/skills/sentry-error-manager/scripts/sentry.sh get <id>
# 3. Resolve stale issues (7+ days no recurrence)
./.claude/skills/sentry-error-manager/scripts/sentry.sh resolve <id1> <id2>
# 1. Assign and comment
./.claude/skills/sentry-error-manager/scripts/sentry.sh assign 7034568465
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment 7034568465 "š INVESTIGATING - analyzing root cause"
# 2. Fix code, commit with Sentry link
git commit -m "fix: resolve error description
Fixes: https://ballee.sentry.io/issues/7034568465/"
# 3. Comment with fix details
./.claude/skills/sentry-error-manager/scripts/sentry.sh comment 7034568465 "š§ FIX DEPLOYED - commit abc123"
# 4. Wait 3+ days, then resolve
./.claude/skills/sentry-error-manager/scripts/sentry.sh resolve 7034568465
| Category | Criteria | Action |
|---|---|---|
| Stale | Last seen > 7 days | Resolve with comment |
| Code Bug | TypeError, ReferenceError | Fix, test, commit |
| Schema Error | PostgREST errors | Fix query or migration |
| External | Network, third-party | Ignore with comment |
| Expected | Rate limiting, validation | Comment as expected |
| Pattern | Fix |
|---|---|
auth.users join error |
Use admin client |
infinite recursion in policy |
Add is_super_admin() bypass |
Classes cannot be passed to Client |
Use JSON.parse(JSON.stringify()) |
Column not found |
Add migration |
ZodError |
Check input matches schema |
docs/investigations/sentry-treatment-log.md/sentry - Quick accesssentry-fixer-agent - Full fix workflowThis section documents how Sentry is configured and integrated in the Ballee codebase.
| File | Purpose |
|---|---|
sentry.client.config.ts |
Browser-side error tracking |
sentry.server.config.ts |
Server-side error tracking |
sentry.edge.config.ts |
Edge runtime error tracking |
instrumentation.ts |
Initializes Sentry based on runtime |
Next.js uses error.tsx files to catch and handle errors at different route levels:
app/
āāā error.tsx # Root fallback (catches all unhandled)
āāā (auth)/error.tsx # Auth routes
āāā (marketing)/error.tsx # Public marketing pages
āāā admin/error.tsx # Admin section root
ā āāā clients/error.tsx # Client management
ā āāā events/error.tsx # Event management
ā āāā settings/error.tsx # Settings
āāā home/
āāā (user)/error.tsx # Personal account
ā āāā inbox/error.tsx # Inbox/chat
ā āāā events/error.tsx # User events
āāā [account]/error.tsx # Team account
āāā events/error.tsx # Team events
āāā settings/error.tsx # Team settings
Current coverage: ~17 error.tsx files across ~118 pages
| Section | Coverage | Error Boundaries |
|---|---|---|
| Root | Full | app/error.tsx |
| Auth | Full | app/(auth)/error.tsx |
| Marketing | Full | app/(marketing)/error.tsx |
| Admin | Partial | Root + key subsections |
| Home (User) | Partial | Root + inbox/events |
| Home (Team) | Partial | Root + events/settings |
Gaps: Some admin subsections rely on parent boundaries. Error propagates up until caught.
When adding an error.tsx to a route:
'use client';
import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Report to Sentry (if not already captured)
Sentry.captureException(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
<h2 className="text-xl font-semibold">Something went wrong</h2>
<p className="text-muted-foreground">{error.message}</p>
<button
onClick={reset}
className="px-4 py-2 bg-primary text-primary-foreground rounded"
>
Try again
</button>
</div>
);
}
Server actions should NOT throw errors directly. Use the Result pattern:
// ā
CORRECT - Returns error in result
export const myAction = withAuth(async (params, data) => {
try {
const result = await service.doSomething(data);
if (!result.success) {
return { success: false, error: result.error.message };
}
return { success: true, data: result.data };
} catch (error) {
// Sentry captures via instrumentation
Sentry.captureException(error);
return { success: false, error: 'An unexpected error occurred' };
}
});
// ā WRONG - Throws error (causes client-side "Load failed")
export const badAction = withAuth(async (params, data) => {
const result = await service.doSomething(data);
if (!result.success) {
throw new Error(result.error.message); // Don't do this!
}
return result.data;
});
| Error Type | Captured By | Shows In |
|---|---|---|
| Client JS errors | sentry.client.config.ts |
Browser errors |
| Server errors | sentry.server.config.ts |
Server errors |
| Database errors | Server instrumentation | Server errors |
| Network failures | Client (if in browser) | Varies |
Some errors are environmental and not code bugs:
| Error | Cause | Action |
|---|---|---|
TypeError: Load failed |
Network interruption during server action | Monitor, no fix needed |
Could not load "util" |
Browser extension conflict | Ignore with comment |
Access denied by security policy |
RLS policy correctly blocking | Verify policy is correct |
{ success, data/error }, not throw.# Check if route has error boundary
ls apps/web/app/path/to/route/error.tsx
# Find all error boundaries
find apps/web/app -name "error.tsx" | head -20
# View Sentry config
cat apps/web/sentry.client.config.ts
The mobile app uses a comprehensive Sentry architecture with multiple layers of error capture.
apps/mobile/lib/core/sentry/
āāā sentry_service.dart # Main service with initialization
āāā sentry_config.dart # Configuration constants
āāā user_context_manager.dart # User identification
āāā filters/
ā āāā pii_filter.dart # PII sanitization
āāā interceptors/
ā āāā sentry_dio_interceptor.dart # HTTP request/response tracking
āāā observers/
ā āāā sentry_navigator_observer.dart # Navigation breadcrumbs
ā āāā sentry_riverpod_observer.dart # Provider error tracking
āāā widgets/
āāā sentry_error_boundary.dart # Error boundary with feedback
By default, SentryFlutter.init only captures uncaught exceptions. In Flutter with Riverpod, many errors are caught by the framework:
AsyncValue.error state but don't propagate as uncaught exceptionsLocation: apps/mobile/lib/core/sentry/sentry_service.dart
Production-grade initialization with:
beforeSend hook// In main.dart
await SentryService.initialize(
dsn: env.sentryDsn,
environment: env.name,
appRunner: () => run(sharedPrefs, useSentryObserver: true),
);
Location: apps/mobile/lib/core/sentry/observers/sentry_riverpod_observer.dart
Captures errors from Riverpod providers:
PostgrestException from Supabase queriesAuthException from authentication failuresStorageException from file upload errorsProviderScope(
observers: useSentryObserver ? [SentryRiverpodObserver()] : [],
child: MyApp(...),
)
Location: apps/mobile/lib/core/sentry/interceptors/sentry_dio_interceptor.dart
Tracks HTTP requests and captures failures:
HttpClient automaticallyLocation: apps/mobile/lib/core/sentry/observers/sentry_navigator_observer.dart
Tracks navigation for error context:
current_screen tag on errors// In router.dart
observers: [
if (SentryService.isInitialized) BalleeSentryNavigatorObserver(),
],
Location: apps/mobile/lib/core/sentry/user_context_manager.dart
Associates errors with users:
// In user_state_notifier.dart
await SentryUserContextManager.setUser(user); // On login
await SentryUserContextManager.clearUser(); // On logout
Location: apps/mobile/lib/core/sentry/filters/pii_filter.dart
Sanitizes sensitive data before sending to Sentry:
Location: apps/mobile/lib/core/sentry/widgets/sentry_error_boundary.dart
Error boundary widget with user feedback:
SentryUserFeedbackSentryErrorBoundary(
child: SomeWidget(),
)
| Option | Value | Reason |
|---|---|---|
errorSampleRate |
1.0 | Capture 100% of errors |
tracesSampleRate |
0.2 | Sample 20% for performance |
maxBreadcrumbs |
100 | Full context trail |
anrTimeout |
5 seconds | Detect frozen UI |
enableNativeCrashHandling |
true | iOS/Android crashes |
| Exception Type | Source | Example Cause |
|---|---|---|
PostgrestException |
Database queries | RLS policy violation, constraint error |
AuthException |
Authentication | Invalid token, expired session |
StorageException |
File storage | Bucket not found, permission denied |
| Error Source | Component | Auto-Captured |
|---|---|---|
| Uncaught Flutter errors | SentryService zone guard |
Yes |
| Riverpod provider errors | SentryRiverpodObserver |
Yes |
| HTTP 5xx/4xx errors | SentryDioInterceptor |
Yes |
| Navigation context | BalleeSentryNavigatorObserver |
Yes (breadcrumbs) |
| Widget subtree errors | SentryErrorBoundary |
Yes |
| Try-catch swallowed | Manual capture needed | No |
For errors in try-catch blocks that shouldn't be rethrown:
import 'package:ballee/core/sentry/sentry_service.dart';
try {
await someOperation();
} catch (e, stackTrace) {
// Report to Sentry but handle gracefully
await SentryService.captureException(e, stackTrace: stackTrace);
// Show user-friendly error
showSnackBar('Operation failed');
}
# View Sentry service
cat apps/mobile/lib/core/sentry/sentry_service.dart
# View Riverpod observer
cat apps/mobile/lib/core/sentry/observers/sentry_riverpod_observer.dart
# View HTTP interceptor
cat apps/mobile/lib/core/sentry/interceptors/sentry_dio_interceptor.dart
# Check for manual Sentry captures
grep -r "SentryService.captureException" apps/mobile/lib
# View all Sentry components
ls -la apps/mobile/lib/core/sentry/