feat: capture unhandled exceptions as deduplicated, scrubbed AppErrors - #2
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
IIssueTrackerseam 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
AppErrorEF config (unique index onFingerprint) + DbSet +AddAppErrorsmigration.AppErrorService— own DB scope, best-effort (like the audit log), upsert by fingerprint: insert first sighting, bumpOccurrences/LastSeenAton recurrence, reopen if resolved.IIssueTracker+NullIssueTrackerseam.Host
ErrorCaptureMiddleware— sits below the exception handler, records then rethrows (never masks the original response). Best-effort./api/_test/throwto exercise the path end-to-end.Verification
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.