Reference notes move to information/, unstarted feature write-ups to new-features/, so the repo root stops being a pile of markdown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 KiB
New observability feature
Collect errors and crashes from frontend and backend, run nightly batch analysis via Opus to categorize and suggest fixes, and surface tickets for manual resolution.
High-level flow
- Capture: Frontend/backend emit errors → POST to
/errorsendpoint → stored in SQLite - Cleanup: When a ticket is created to address an error group, delete the raw error logs for that group
- Nightly categorization job: Query new errors → classify by category
- Nightly analysis job: Group errors by category + location → send to Opus via Batches API → generate tickets
- Frontend dashboard: View tickets, filter, search, edit, delete
1. Frontend error capture
TypeScript SDK
Lightweight error capture library (part of Föräldraledig Plannerare React/Vite frontend).
Functionality:
- Catch global errors:
window.onerror,unhandledrejection - Provide explicit
captureError(error, context?)function for manual logging - Attach context: current URL, user session ID (if available), browser/device info
- Queue errors with exponential backoff + retry logic before POSTing
- POST to the app's existing
/errorsendpoint (backend defined below) - No external dependencies; vanilla TypeScript
Captured fields:
error_message(e.g., "Cannot read property 'x' of undefined")stack_trace(full JS stack)url(current page URL)user_agent(browser + OS)timestampsession_id(if available)environment(e.g., "production", "staging")
2. Backend error endpoint
/errors POST endpoint
Location: Föräldraledig Plannerare existing backend
Request body:
{
"error_message": "string",
"stack_trace": "string",
"url": "string",
"user_agent": "string",
"timestamp": "ISO8601",
"session_id": "string or null",
"environment": "string"
}
Behavior:
- Validate and log the error
- Store in SQLite
errorstable (schema below) - Return 200 OK immediately (don't block the client)
- Optionally rate-limit per session_id or IP to prevent spam
3. SQLite schema
errors table
CREATE TABLE errors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL, -- ISO8601, when error occurred
environment TEXT NOT NULL, -- 'production', 'staging', etc.
error_message TEXT NOT NULL, -- e.g., "Cannot read property 'x'"
stack_trace TEXT NOT NULL, -- Full stack trace
endpoint TEXT, -- URL or backend route (e.g., '/api/users')
location TEXT, -- File:line or component name (extracted from stack)
user_agent TEXT, -- Browser + OS
session_id TEXT, -- User session identifier
error_level TEXT, -- 'error', 'warning', 'fatal'
error_type TEXT, -- 'frontend' or 'backend'
severity INTEGER DEFAULT 1, -- 1=low, 2=medium, 3=high (Opus can set this)
error_hash TEXT, -- SHA256(stack_trace) for quick deduplication
first_seen TEXT, -- ISO8601, first occurrence of this group
last_seen TEXT, -- ISO8601, most recent occurrence
occurrence_count INTEGER DEFAULT 1, -- How many times in the time window
category TEXT, -- Set by categorization job (NULL initially)
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
has_ticket INTEGER DEFAULT 0 -- 1 if a ticket exists for this error group, 0 otherwise
);
CREATE INDEX idx_timestamp ON errors(timestamp);
CREATE INDEX idx_category ON errors(category);
CREATE INDEX idx_error_hash ON errors(error_hash);
CREATE INDEX idx_has_ticket ON errors(has_ticket);
tickets table
CREATE TABLE tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- Auto-generated by Opus, editable by user
description TEXT NOT NULL, -- How to fix, what changed, Opus analysis
error_category TEXT NOT NULL, -- The category this ticket addresses
error_location TEXT NOT NULL, -- Where in the code (e.g., 'frontend/auth', 'backend/api/users')
error_type TEXT NOT NULL, -- 'frontend' or 'backend'
error_count INTEGER, -- Total occurrences in the period
opus_suggestion TEXT, -- Raw suggestion from Opus
code_context TEXT, -- Git diff, recent commits, relevant file snippets
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'open', -- 'open', 'in_progress', 'resolved', 'ignored'
resolved_at TEXT, -- When marked resolved
related_errors TEXT -- JSON array of error IDs this ticket groups
);
CREATE INDEX idx_status ON tickets(status);
CREATE INDEX idx_created_at ON tickets(created_at);
4. Predefined error categories
The categorization job uses these predefined categories as a starting point. Opus is encouraged to suggest new categories if errors don't fit these patterns.
Predefined categories:
- Auth failure — Authentication, authorization, token expiry, invalid credentials
- Null/undefined reference — Null pointer, undefined property access, type mismatches
- Network error — Fetch/HTTP failures, timeouts, 4xx/5xx responses, DNS issues
- Rate limit — 429 responses, quota exceeded, throttling
- Validation error — Input validation, schema mismatches, malformed data
- Resource not found — 404, missing file/endpoint, nonexistent record
- Unhandled exception — Unexpected errors, missing error handlers, caught but not handled
- Performance/timeout — Slow operations, stack overflow, memory issues, request timeouts
- Data integrity — Constraint violations, inconsistent state, corrupted records
- Configuration/setup — Missing env vars, incorrect configuration, initialization failures
New categories: If Opus encounters errors that don't fit these, it should suggest a new category in the ticket description. Over time, review and add new predefined categories.
5. Nightly categorization job
Frequency: Nightly (e.g., 1 AM)
Trigger: Manually via cron or scheduled task runner (e.g., APScheduler in Python)
Steps:
- Query all errors where
category IS NULLandcreated_at>= last run timestamp - For each error:
- Extract likely category from
error_message,stack_trace, anderror_level - Use simple regex or rule-based matching to assign a predefined category
- If no match, leave
category = NULLfor the analysis job to handle
- Extract likely category from
- Commit changes to
errors.category
Example logic:
- If message contains "Unauthorized" or "403" → "Auth failure"
- If message contains "null is not an object" or "Cannot read property" → "Null/undefined reference"
- If message contains "timeout" or "ECONNREFUSED" → "Network error"
- If HTTP status 429 → "Rate limit"
6. Nightly analysis and ticket generation job
Frequency: Nightly (e.g., 1:30 AM, after categorization job)
Tool: Python with Anthropic SDK, using the Batches API for cost efficiency
Steps:
6.1 Group errors
Query all errors where has_ticket = 0 and category IS NOT NULL (use categorization job results).
Grouping strategy: Errors are grouped by (category, error_type, location) to avoid treating the same class of error (e.g., "Null/undefined reference in frontend/auth") as separate issues across occurrences.
category: The classified error type (e.g., "Null/undefined reference")error_type: Whether error originated in frontend or backendlocation: Where in the code the error occurred (extracted from stack trace, e.g.,components/LoginForm.tsxorservices/user_service.py)
Deduplication within a group:
- Count total occurrences of errors in this group (sum
occurrence_countfrom all matching error rows) - Collect representative error instances (e.g., top 3 most recent or most severe)
- Discard duplicate raw instances within the group to avoid redundant analysis
- Opus sees only a count + sample errors, not every instance
Output: A list of error groups, each with:
categoryerror_typelocationtotal_count(total occurrences)sample_errors(list of representative error details)time_window(when these errors occurred)
6.2 Gather context for Opus
For each error group, collect surrounding code and git context so Opus can make informed suggestions:
For frontend errors:
- The exact error messages and stack traces from the sample errors
- Extract file path + line number from stack trace
- Fetch the relevant code snippet from the file (±10 lines from the error location)
- Get recent commits affecting that file:
git log --oneline -n 10 -- <file> - Get git diff of recent changes:
git diff HEAD~5 -- <file>(last 5 commits) - Browser/environment info from user_agent
For backend errors:
- The exact error messages and stack traces
- Extract file path + line number from stack trace
- Fetch the relevant code snippet from the file (±10 lines from the error location)
- Get recent commits affecting that file:
git log --oneline -n 10 -- <file> - Get git diff of recent changes:
git diff HEAD~5 -- <file> - Related logs or request context (if available)
Data structure sent to Opus:
{
"category": "Null/undefined reference",
"error_type": "frontend",
"location": "components/LoginForm.tsx",
"total_count": 47,
"time_window": "2026-09-18 to 2026-09-19",
"sample_errors": [
{
"error_message": "Cannot read property 'email' of undefined",
"stack_trace": "...",
"timestamp": "2026-09-19T02:15:00Z",
"url": "http://app/login"
},
{
"error_message": "Cannot read property 'email' of undefined",
"stack_trace": "...",
"timestamp": "2026-09-19T01:30:00Z",
"url": "http://app/login"
}
],
"code_context": {
"file": "components/LoginForm.tsx",
"snippet": "const LoginForm = ({ user }) => {\n return <div>{user.email}</div>;\n}",
"recent_commits": [
"abc1234 - Fix login form prop handling",
"def5678 - Update auth flow"
],
"git_diff": "..."
}
}
6.3 Send to Opus via Batches API
Batch all error groups into a single Batches API request (for cost efficiency).
Prompt for Opus:
You are analyzing error logs from a web application (Föräldraledig Plannerare).
For each error group below, provide:
Root cause analysis
Suggested fix (code changes or steps)
Affected systems/users
Priority (low, medium, high)
Error group:
{error data from 6.2}
Respond in JSON format:
{
"title": "Brief title for the issue",
"root_cause": "What caused this",
"suggested_fix": "How to fix it (code changes or steps)",
"affected_scope": "Frontend/backend, number of users, systems impacted",
"priority": "low|medium|high",
"reasoning": "Why this priority"
}
6.4 Parse Opus response and create tickets
For each error group with Opus analysis:
-
Create a row in
ticketstable with:title= Opus's suggested titledescription= Combination of root cause, suggested fix, and affected scopeerror_category= The error group's categoryerror_location= The file/component locationerror_type= frontend or backenderror_count= total_count from the groupopus_suggestion= Full raw response from Opuscode_context= Git diff and file snippets (for reference)related_errors= JSON array of error IDs in this group
-
Update all error rows in the group:
has_ticket = 1 -
After ticket creation: Delete all error rows where
has_ticket = 1(cleanup raw errors once addressed by a ticket)
7. Frontend dashboard MVP
Technology: React + Vite (part of Föräldraledig Plannerare frontend)
Access: Run with npm run observability or similar command
Features:
View
- Tickets list: All open/in-progress tickets (paginated, ~20 per page)
- Ticket detail: Expand to show full description, error context, code snippets, priority
- Filter:
- By status: open, in_progress, resolved, ignored
- By error_type: frontend, backend
- By error_category: (all predefined categories + custom)
- By date range
- Search: By ticket title or description (substring match)
- Trends chart: Line chart showing error count over time (last 7 days)
Actions
- Edit ticket: User can update
titleanddescription(e.g., clarify the fix, add notes) - Change status: Mark as in_progress, resolved, ignored
- Delete ticket: Remove a bad or duplicate ticket
- View related errors: Show the raw error logs (with timestamps, counts) that this ticket groups
Data source
- All ticket data queried from backend
/ticketsGET endpoint (with optional filters) - All error data queried from backend
/errorsGET endpoint (with filters)
8. Backend API endpoints (existing app backend)
POST /errors
Receive error reports from the frontend SDK
Request: Error object (see Section 2)
Response: { success: true }
GET /tickets
Fetch tickets with optional filters
Query params:
status(open, in_progress, resolved, ignored)error_type(frontend, backend)error_category(predefined category name)date_from(ISO8601)date_to(ISO8601)search(substring search in title/description)page(pagination, default 1)limit(per page, default 20)
Response:
{
"tickets": [
{
"id": 1,
"title": "Login form crashes on null user object",
"description": "...",
"error_category": "Null/undefined reference",
"error_type": "frontend",
"error_count": 47,
"status": "open",
"created_at": "2026-09-19T01:45:00Z",
"code_context": "..."
}
],
"total": 42,
"page": 1
}
GET /errors
Fetch raw error logs (for dashboard context)
Query params:
ticket_id(optional; show errors related to a specific ticket)category(optional; filter by category)error_type(optional; frontend or backend)date_from,date_to(optional; filter by date range)has_ticket(optional; 0 or 1)limit(default 100)
Response:
{
"errors": [
{
"id": 123,
"timestamp": "2026-09-19T02:15:00Z",
"error_message": "Cannot read property 'email' of undefined",
"location": "components/LoginForm.tsx",
"occurrence_count": 5,
"category": "Null/undefined reference"
}
],
"total": 200
}
PATCH /tickets/:id
Update a ticket (title, description, status)
Request:
{
"title": "Updated title",
"description": "Updated description",
"status": "in_progress"
}
Response: Updated ticket object
DELETE /tickets/:id
Delete a ticket
Response: { success: true }
After deletion, the related error rows should have has_ticket = 0 set again (so they can be re-analyzed in a future batch run if desired).
9. Summary of jobs and scheduling
| Job | Frequency | Trigger | Input | Output |
|---|---|---|---|---|
| Categorization | Nightly (1 AM) | Cron/scheduler | Uncategorized errors | Errors with category set |
| Analysis + ticket generation | Nightly (1:30 AM) | Cron/scheduler | Categorized errors + git context | New tickets, deletion of raw errors with tickets |
10. Implementation notes
- Python setup: Use Anthropic Python SDK for Batches API calls
- Scheduling: Use APScheduler or similar to run jobs on a schedule
- Error deduplication: Use SHA256(stack_trace) for quick lookups; grouping by (category, error_type, location) for analysis
- Cost efficiency: Batches API ensures Opus analysis is cost-effective at scale
- Self-hosting: All components (SDKs, backend, jobs, frontend) are self-hosted on the existing Föräldraledig Plannerare infrastructure