Skip to content

feat(stability): Phase 2 — eliminate silent failures - #1

Open
dariusvorster wants to merge 16 commits into
mainfrom
stability/phase2-silent-failures
Open

feat(stability): Phase 2 — eliminate silent failures#1
dariusvorster wants to merge 16 commits into
mainfrom
stability/phase2-silent-failures

Conversation

@dariusvorster

Copy link
Copy Markdown
Owner

Summary

  • 2A: Structured pino logger with ring buffer; all console.* replaced across packages
  • 2B: Explicit error handling on every external call — Caddy, Docker (3-attempt retry), SQLite UNIQUE → CONFLICT, Cloudflare fetch
  • 2C: Result<T, E> discriminated union in @proxyos/types; applied to DDNS/LockBox operations
  • 2D: All tRPC mutations throw TRPCError with correct codes (NOT_FOUND, CONFLICT, PRECONDITION_FAILED, etc.) and actionable messages
  • 2E: useErrorHandler hook; every useMutation in the web app has onError
  • 2F: System logs UI page (ring buffer viewer, 5s polling, admin-protected download)
  • 2G: /api/health/detailed + System Health UI page (30s refresh, per-component status)
  • 2H: /api/auth/diagnose endpoint + login page diagnosis link + improved UNAUTHORIZED message

Test Plan

  • 59 caddy vitest tests pass (cd packages/caddy && npx vitest run)
  • Zero bare catch {} patterns without comments in packages/api and packages/caddy
  • All useMutation calls have onError (grep useMutation apps/web/src | grep -v onError returns nothing)
  • /api/health/detailed returns component-level JSON when authenticated as admin
  • System Logs page loads at /logs (Pino logs tab present)
  • System Health page loads at /system-health
  • Download button at /logs requires admin auth (401 if not authenticated)
  • Login page shows "Run diagnosis" link after a failed login attempt

🤖 Generated with Claude Code

Darius Vorster and others added 16 commits April 22, 2026 08:51
- New package @proxyos/logger: createLogger(subsystem) returns pino logger
  with pretty-print in dev, JSON in production
- Replaced all console.log/warn/error in packages/caddy/src/bootstrap.ts,
  packages/api/src/{auth,crypto,loader,bootstrap}.ts,
  packages/api/src/routers/{routes,automation}.ts,
  packages/api/src/automation/{drift-detector,network-join}.ts,
  packages/api/src/security/exit-node-blocker.ts,
  packages/alerts/src/evaluator.ts,
  apps/web/src/instrumentation.node.ts
- Locked files (trpc.ts, users.ts, config.ts, verify.ts, regenerate-routes.ts) untouched
- 59 caddy vitest tests still pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move pino-pretty from dependencies to devDependencies in packages/logger
- Update createLogger to use transport:undefined in production so pino-pretty is never resolved at runtime
- Add @proxyos/logger workspace dep to packages/db

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hes (2A)

Move pino-pretty from devDependencies to dependencies so staging/dev
containers that skip devDep installs don't crash. Pass { err } as the
first arg in all four bootstrap.ts catch blocks so pino serialises the
error stack in structured output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docker-discovery: add HTTP status check + descriptive errors to fetchContainers; log (not swallow) sync failures via pino
- network-join: split dockerRequest into dockerRequestOnce + retry wrapper with 3 attempts at 1s/2s/4s for transient errors (ECONNRESET, ETIMEDOUT, etc); permanent 404/403 errors skip retry
- ddns.ts + ddns-updater.ts: add response.ok checks on Cloudflare zones and records API calls before .json(); wrap fetch with .catch for network failures
- routes.ts: wrap all three insert(routes) sites with UNIQUE constraint catch → TRPCError CONFLICT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…perations (2C)

- Export Result<T, E = Error> from @proxyos/types
- detectPublicIp (ddns-updater): was Promise<string|null>, now Result<string,Error> — caller reads ipResult.ok
- fetchFromLockBox (lockboxos): was Promise<string|null>, now Result<string,Error> — caller in integrations.ts updated
- updateCloudflare (routers/ddns): was Promise<string|null> (inverted: string=error, null=ok), now Result<void,Error> — triggerUpdate procedure updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- routes.ts: add messages to bare NOT_FOUND throws (get, update); wrap
  bare `throw err` re-throws after DB insert as INTERNAL_SERVER_ERROR
  with [db] prefix
- trafficReplay.ts: add messages to NOT_FOUND throws (record, replay)
- ddns.ts: add messages to NOT_FOUND throws (update, triggerUpdate);
  convert bare throw new Error in updateCloudflare .catch() chains to
  TRPCError via toCaddyFetchError helper
- caddy.ts: convert throw new Error(status) in rootCA to TRPCError with
  descriptive [caddy] message
- integrations.ts: convert throw new Error in testLockBoxRef to
  NOT_FOUND / BAD_REQUEST TRPCError; add TRPCError import
- organizations.ts: convert throw new Error in get to NOT_FOUND
  TRPCError; add TRPCError import
- sites.ts: convert throw new Error in get to NOT_FOUND TRPCError;
  add TRPCError import
- templates.ts: convert throw new Error for built-in delete guard to
  CONFLICT TRPCError with template ID; add TRPCError import
- automation.ts: convert throw new Error in toggleComposeWatcher to
  NOT_FOUND TRPCError; add TRPCError import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every useMutation in the web app now has onError: handleError wired
from useErrorHandler, ensuring mutation errors surface to the user
rather than failing silently. Covers all routes, settings, security,
scanner, shell sidebar, and TOTP sub-components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add in-memory ring buffer (1000 entries) to @proxyos/logger with getRecentLogs/clearLogs; pino.multistream feeds both stdout and the buffer
- Add system.getLogs adminProcedure filtering by subsystem and level
- Add Pino logs tab to /logs page with 5s polling, level/subsystem filters, and Download button
- Add GET /api/logs/download route returning NDJSON attachment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add checkDatabase/checkCaddy/checkDocker/checkAuth/checkDisk helpers to packages/api/src/routers/system.ts
- Add getDetailedHealth adminProcedure to systemRouter
- Add ./health-checks subpath export to packages/api/package.json
- Create apps/web/src/app/api/health/detailed/route.ts (admin-only REST wrapper)
- Create apps/web/src/app/system-health/page.tsx (30s auto-refresh UI)
- Add System health link to Tools nav in shell.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /api/auth/diagnose returns structured {authenticated, reason, action, details} alongside legacy status/hint fields for backward compat
- Login page shows "Not sure why? Run diagnosis" link on credential failures; expands inline panel with hint from diagnose endpoint
- useErrorHandler UNAUTHORIZED message now suggests logging out and back in

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant