Skip to content

Enhance error capture with detailed context and cause chains - #258

Merged
jalexw merged 2 commits into
mainfrom
claude/zen-lamport-3gq7q1
Oct 12, 2026
Merged

jalexw merged 2 commits into
mainfrom
claude/zen-lamport-3gq7q1

Conversation

@jalexw

@jalexw jalexw commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR significantly improves error reporting and debugging by enhancing the captureServerException function to capture richer error context, including cause chains, aggregated errors, and error-specific properties (like database error codes). It also introduces reportServerException for code paths without a database handle, and adds comprehensive error handling throughout the codebase.

Key Changes

Core Error Capture Infrastructure

  • buildErrorRow(): New exported function that constructs error rows with:

    • Full error cause chains and AggregateError members rendered into the stack
    • Error-specific properties (e.g., Postgres code, detail, constraint) captured as error_details in context
    • NUL character replacement (Postgres TEXT/JSONB rejection)
    • UUID validation for the uid field (invalid UUIDs moved to context)
    • JSON serialization with bigint/cycle/depth handling
  • reportServerException(): New module for error reporting without a database handle in scope:

    • Opens its own serverless database connection
    • Supports throttling (e.g., max once per 10 seconds per operation) to prevent log spam during outages
    • Used by library helpers, Server Components, and Next.js hooks
  • captureServerException(): Refactored to use buildErrorRow() for cleaner separation of concerns

Error Handling Improvements

  • API error handling (src/lib/api/app.ts):

    • Captures errors from route middleware and preflight handlers (previously uncaught)
    • Reuses request's database handle when available; falls back to reportServerException
    • Properly closes database handles even on error
  • Token verification (JWKS, refresh tokens, userinfo):

    • Separated error handling for key import vs. token verification
    • Key import failures (stored key issues) are recorded to ERRORS table
    • Token verification failures remain silent (caller's assertion, not server's problem)
  • Route guards (RouteGuardJwtKeysManager):

    • Wraps key manager to record keyset load failures with throttling
    • Prevents database outages from appearing as user logouts
  • Password verification: Captures argon2 verification failures with context

  • Redis failures: Rate limiter records Redis outages (once per minute per limiter)

Testing

  • captureServerException.test.ts: Comprehensive tests for buildErrorRow() covering:

    • Cause chains and AggregateError rendering
    • Cycle detection
    • Database error field extraction
    • NUL character replacement
    • Non-UUID uid handling
    • Non-Error thrown values
  • app.test.ts: Tests for API error capture in middleware and preflight handlers

Next.js Integration

  • instrumentation.ts: New file with onRequestError hook to capture:
    • Server Component rendering errors
    • Route handler failures outside the API runtime
    • Route module load failures
    • Skips prerendering phase (no database available)

Version Bumps

  • @schemavaults/auth-server: 0.55.2 → 0.55.3
  • @schemavaults/auth-server-sdk: 0.32.5 → 0.32.6
  • @schemavaults/trpc-backend-init: 0.9.76 → 0.9.77

Notable Implementation Details

  • Error cause chains are limited to 5 levels deep to prevent unbounded recursion
  • JSON serialization depth is capped at 8 levels
  • Cycle detection prevents infinite loops in error chains
  • Error details are extracted via Object.entries() with try/catch for throwing getters
  • NUL characters are replaced with the Unicode replacement character (U+FFFD)
  • Context can be a plain object, array, string, or null; non-objects are wrapped when adding error details
  • Database transactions passed to captureServerException must not be rolled back afterward (ERRORS row would be lost)

https://claude.ai/code/session_01MAzy4APcqDFwdYmCGUqXpu

…- record errors that never reached captureServerException

Many serious failures never reached the ERRORS table because they were
caught and answered as something else, or thrown where nothing records:

- the sink itself: causes and AggregateError members were dropped, as
  were Postgres error fields; array/string contexts, NUL characters and
  non-uuid uids made the insert fail
- the API runtime: errors in a route's preflight / middleware went to
  Hono's default handler; a failure reported after the request context
  was released opened a database handle nothing closed
- Server Components and other route handlers: no onRequestError hook
- operation catch-alls answering 500 without recording
- infrastructure failures answered as caller errors: route guard key
  loading and revocation lookups (user looks signed out), refresh grant
  keyset lookup (invalid_grant made clients drop good refresh tokens; it
  is now a recorded 500), userinfo, introspection, JWKS access
  assertions, authorize app lookup, audience checks, argon2 verification
- silently degraded security: rate limits failing open on Redis errors,
  failed watermark cache invalidation, MFA alert emails (two were sent
  fire-and-forget after the request's db/redis were released)
- pages that redirect to an error page or render defaults on failure

Adds reportServerException() (opens its own db handle, optional
throttle) for code without a handle, and keeps the original error as
`cause` on rethrows along the token-verification and settings paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAzy4APcqDFwdYmCGUqXpu
…q7q1

main already shipped auth-server-sdk 0.32.6/0.32.7, trpc-backend-init
0.9.77/0.9.78 and auth-server 0.55.3-0.55.5, so this branch's bumps move
to auth-server-sdk 0.32.8, trpc-backend-init 0.9.79, auth-server 0.55.6.
whoami keeps main's client-app refresh-token bearer scheme alongside the
error capture import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAzy4APcqDFwdYmCGUqXpu
@jalexw jalexw self-assigned this Oct 10, 2026
@jalexw
jalexw merged commit 576896d into main Oct 12, 2026
35 checks passed
@jalexw
jalexw deleted the claude/zen-lamport-3gq7q1 branch October 12, 2026 18:16
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