Skip to content

feat: capture unhandled exceptions as deduplicated, scrubbed AppErrors - #2

Merged
asharahmed merged 2 commits into
mainfrom
feat/error-capture
Jun 29, 2026
Merged

feat: capture unhandled exceptions as deduplicated, scrubbed AppErrors#2
asharahmed merged 2 commits into
mainfrom
feat/error-capture

Conversation

@asharahmed

Copy link
Copy Markdown
Owner

Why

First half of capture → Linear: turn runtime faults into tracked, deduplicated records — the unit a Linear issue (and later an auto-fix attempt) is created from. Built on the verification substrate from #1, so the capture path is proven against real SQL, not asserted in the abstract.

Linear delivery is the next, additive stage; the IIssueTracker seam is in place now with a no-op default, so wiring Linear later needs no change to the capture path.

What

Domain (pure, unit-tested — +12 tests, 76 total)

  • AppError + AppErrorStatus.
  • ErrorFingerprint — stable identity = hash(exception type + normalized top frames); strips file/line so an unrelated edit doesn't fork the group.
  • SecretScrubber — redacts API keys / bearer / JWT / long high-entropy runs. Capturing a bug must never leak a credential — the "never log secrets" rule applied to the new sink.

Infrastructure

  • AppError EF config (unique index on Fingerprint) + DbSet + AddAppErrors migration.
  • AppErrorService — own DB scope, best-effort (like the audit log), upsert by fingerprint: insert first sighting, bump Occurrences/LastSeenAt on recurrence, reopen if resolved.
  • IIssueTracker + NullIssueTracker seam.

Host

  • ErrorCaptureMiddleware — sits below the exception handler, records then rethrows (never masks the original response). Best-effort.
  • Non-Production /api/_test/throw to exercise the path end-to-end.

Verification

  • ✅ Full solution builds clean; 76/76 unit tests (the fingerprint/scrub logic).
  • ⏳ The capture integration test runs in CI (real SQL via Testcontainers): asserts an unhandled exception is captured, the secret in its message is redacted, the query string is not stored, and a recurrence dedups to one row with Occurrences == 2. Could not run locally (no Docker) — CI is the proof, as with test: real integration + E2E (Playwright) verification tiers #1.

Not in this PR (next)

Linear delivery (config-gated LinearIssueTracker + an admin view of captured errors), then the agent.

The first half of capture -> Linear: turn runtime faults into tracked,
deduplicated records — the unit a Linear issue (and later an auto-fix
attempt) is created from. Linear delivery is the next, additive stage;
the IIssueTracker seam is in place with a no-op default.

- Domain (pure, unit-tested): AppError entity + AppErrorStatus; ErrorFingerprint
  (stable hash from exception type + normalized top frames, line-number-stable);
  SecretScrubber (redact api keys / bearer / JWT / long high-entropy runs) so
  capturing a bug can never leak a credential. +12 unit tests (76 total).
- Infrastructure: AppError EF config (unique index on Fingerprint) + DbSet +
  AddAppErrors migration; AppErrorService (own scope, best-effort, upsert-by-
  fingerprint: insert first sighting, bump Occurrences/LastSeenAt on recurrence,
  reopen if resolved); IIssueTracker + NullIssueTracker seam.
- Host: ErrorCaptureMiddleware below the exception handler (records then rethrows
  — never masks the original response); non-Production /api/_test/throw to
  exercise the path.
- Integration test (real SQL via the harness): unhandled exception is captured,
  the secret in its message is redacted, the query string is not stored, and a
  recurrence dedups to one row with Occurrences == 2.
…e test

WebApplicationFactory's TestServer rethrows the unhandled exception to the
caller (the /Error re-execution can't complete in-memory) instead of the 500
a real server returns. Capture still happens below the handler before the
rethrow; the test now accepts either outcome and asserts the recorded row.
@asharahmed
asharahmed merged commit 3741c25 into main Jun 29, 2026
8 of 10 checks passed
@asharahmed
asharahmed deleted the feat/error-capture branch June 29, 2026 01: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.

1 participant