fix(runner): give the Tier-2 build-failure envelope a constant title (DEV-2876) - #333
Merged
Merged
Conversation
…V-2876) Tier-2 build-failure envelopes were already flat-fingerprinted by normalizeMonitorMessage's ISO rule (2464f33), but the raw stderr line was still the Sentry title, and a Sentry issue re-derives its title from the newest event. Since the line embeds a duration and a timestamp, each bucket was permanently named after whichever sample arrived last: DEMOS-5Q is frozen at "Application bundle generation failed. [0.505 seconds] - 2026-09-02T07:40:26.664Z", with the same shape on DEMOS-53, 4Y, 4W, 4V. This is the follow-up DEV-2854 / PR #326 explicitly deferred. Extend tier2StderrReport (apps/authoring/src/tier2Report.ts) with a second, ordered branch: TS diagnostic first (unchanged, byte-identical object literal), then the build envelope (/^Application bundle generation failed\./), else null. The order is load-bearing but not about specificity — it makes the new branch strictly additive over the population the function classified before: nothing previously returned as the compile bucket or as null can be reclassified by the new branch, since it only runs after the TS check has already failed. The two shapes can collide in one stderr line (a forged postMessage can deliver both), so the collision is covered by a test (ENVELOPE_PLUS_TS) rather than dismissed; the compile branch wins the tie because the TS code is the actionable half. The envelope gets its own fingerprint (["demo-runtime", "stderr", "tier2-build"]), distinct from tier2-compile's: the envelope says that the build failed, a diagnostic says why, and merging cause and effect would make both harder to read. The raw line (duration and timestamp included) rides in extra.buildFailure, never in the title or fingerprint. Duration gets no tag: unlike ts_code, which slices the bucket along a fault-class axis, wall time mostly reflects project size and container load, not the defect, so a bucketed duration tag would be a metric wearing a tag's clothes with no named consumer. The ISO timestamp stays in extra as recoverable context (it's the container's own clock, not a duplicate of Sentry's event timestamp) but is likewise not a fault-class discriminator. sentry.ts gets a comment-only update naming both recognised shapes; no code, statement, or import changed there — every field already flows through DEV-2854's wiring. Tests added to pipeline/tier2-report.test.mjs cover both fix-provers (shared fingerprint, constant non-raw title, fingerprint/title distinct from tier2-compile's, verbatim extra.buildFailure with no duration/date leakage, no build_duration tag, tier2-build tag with no ts_code) and guards (bundle-complete, mid-line mention, ::frozen retry::, vite internal errors, the existing NG/Failure-reason/Could-not-resolve set, non-stderr kinds, and the TS-first order guard). All 12 pre-existing tests stay green byte-for-byte since the compile branch's return object is untouched. After deploy, DEMOS-5Q, 53, 4Y, 4W, 4V need bulk-resolving like the DEV-2853/DEV-2854 sets — a fingerprint change only affects new events. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
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.
The follow-up #326 (DEV-2854) explicitly deferred. Triage ergonomics — nothing visitor-facing changes.
The problem
Tier-2 build-failure envelopes were already flat-fingerprinted by commit
2464f3325, vianormalizeMonitorMessage's ISO rule. But the raw message is still the title, and that message embeds a duration and a timestamp — so each bucket is permanently named after one moment:Application bundle generation failed. [0.505 seconds] - 2026-09-02T07:40:26.664Z, on release2472da96, which does contain the fingerprint fix. So the collapse is live and the title flap is what remains.[0.310 seconds], DEMOS-4Y[0.346 seconds], DEMOS-4W[0.238 seconds], DEMOS-4V[1.595 seconds].Identical to the defect #326 fixed for TS diagnostics, documented at
tier1Report.ts:13-18: a Sentry issue re-derives its title from its newest event, so a fingerprint coarser than the message gives you a bucket whose name keeps changing.The fix
A second recogniser in
tier2Report.ts—/^Application bundle generation failed\./— returning a constant"Tier-2 build failed"with the raw line inextra.buildFailure.Two distinct fingerprints, deliberately. The envelope says that the build failed; a TS diagnostic says why.
["demo-runtime","stderr","tier2-build"]versustier2-compile. Merging a cause with its effect would make both harder to read.Constant title iff flat fingerprint — the invariant #326 established. No
"Tier-2 build failed (0.505s)": a duration-varying title on a flat fingerprint flaps exactly like today's does.Anchored, and the suffix is deliberately not required.
^excludes a mid-line prose mention; the literal\.excludesApplication bundle generation complete.; and not requiring[N seconds] - <ISO>means a toolchain version that drops the timing still collapses into the same bucket instead of falling out to a per-message fingerprint.Order is TS-first, and that is load-bearing
Not for specificity — because it makes the new branch strictly additive over the previously-
nullpopulation. No message this function classified before this PR can be reclassified by it, by construction, rather than as a claim about an empty intersection.The collision is reachable rather than theoretical:
kind: "stderr"is inMONITOR_KINDS, so a forgedpostMessagefrom the preview can deliver a multi-line stderr carrying both shapes. It resolves to the compile bucket — the TS code is the actionable half and keepsts_codeas a facet — and there is a test for it rather than a dismissal.Duration and timestamp:
extraverbatim, no tagNothing dropped, nothing faceted. A tag has to earn its cardinality, and
ts_codeearned it by slicing the bucket into fault classes — "how many TS2304s this week" has a real answer. A failing build's wall time measures how far esbuild got before erroring, a function of the visitor's project size and container load, so faceting on it partitions the bucket along an axis orthogonal to the defect. A bucketed"<1s"/"1-5s"tag would be a metric wearing a tag's clothes, with no named consumer.App.tsx:262-269's house rule permits a facet beside the fingerprint; it does not oblige one. And the ISO is the container's clock at build time, not the browser's send time, so it is not a duplicate of Sentry's own event timestamp — kept inextrabecause it is recoverable context, kept out of title and fingerprint because it is not a fault-class discriminator.Distinct
extrakey (buildFailure, notcompileDiagnostic) soextrastays self-describing per bucket.The allowlist stays an allowlist
Second entry added, no denylist. These all still return
null, verified by execution:Application bundle generation complete., a mid-line prose mention,::frozen install failed…::,[vite] Internal server error: …,✘ [ERROR] Could not resolve "./app/App",NG8001,NG8002, andFailure reason:— the parked DEMOS-5B, which appears nowhere in this repo and is deliberately left alone.Verification
pipeline/tier2-report.test.mjs— 21/21 (12 pre-existing + 9 new)pnpm test—1148 tests / 1146 pass / 0 fail / 2 todo(todos pre-existing)pnpm typecheck— clean across all four projectsnode scripts/check-test-presence.mjs master— passFail-on-revert applies here, unlike #326. Reverting deletes the branch, not the module, so the module survives and the fix-provers throw. Confirmed independently, twice:
tier2StderrReport("stderr", BUNDLE_5Q)returnsnullon master today, and disabling the build branch turns exactly tests 13-17 red while guards 18-21 stay green. The order guard is labelled a guard in-file, with a comment saying it passes on revert too — it exists to fail if someone reorders the branches, not to prove the fix.The compile branch did not move. Its
returnobject was restructured into a nestedif, so this was the real risk — if any value drifted, the livetier2-compilebucket regroups and #326's work from earlier today silently comes undone. Independently diffed during review: fingerprint array,tags(including thesingleCodespread),extra.compileDiagnosticanddisplayare byte-identical to master; only indentation changed. All 12 pre-existing tests stayed green throughout.sentry.tsis comment-only — exactly two comment blocks, no statement, import or expression. Every field already flows through #326's wiring (call, tags, fingerprint, extra, display, and thelevelmap that sendsstderr→warning), so the untestable region does not grow here.After deploy — bulk-resolve required
A fingerprint change affects new events only. DEMOS-5Q, 53, 4Y, 4W and 4V stop receiving events and one constant-titled bucket opens beside them. All five need resolving, same as the DEV-2853 and DEV-2854 sets — left alone they sit in triage forever with their frozen timestamps, which is the exact symptom this PR exists to remove.
Noted, out of scope
When a build fails but its cause line does not survive
STDERR_MARKERS(e.g.src/app/app.component.ts:12:34:has no marker word and is filtered), the constant-titled envelope bucket carries no cause at all. That is arelayStderrproperty, not this module's, and the envelope never carried a cause before either.Relay budget untouched on purpose:
demoRelayBudget.admitstill keys on the raw message, so an envelope plus a TS line still cost two ofMONITOR_EVENT_CEILING. AndnormalizeMonitorMessage's ISO rule stays load-bearing for thestderrSeendedupe inrelayStderreven though the Sentry fingerprint no longer depends on it here — do not "simplify" that rule on the strength of this PR.🤖 Generated with Claude Code
Note
Low Risk
Observability-only change to Sentry fingerprint/title for a specific stderr pattern; no user-facing runtime behaviour and existing compile grouping is preserved with tests.
Overview
Extends Tier-2 Sentry grouping (DEV-2876) so build-failure envelope stderr lines (
Application bundle generation failed.…) get the same treatment as TS compiler diagnostics: flat fingerprinttier2-build, constant title Tier-2 build failed, and the full raw line inextra.buildFailureinstead of the issue title.tier2StderrReportis refactored into two allowlisted branches (TS diagnostic first, then envelope) with separate fingerprints fromtier2-compileso “build failed” and “why it failed” do not merge. Compile-path behaviour is unchanged aside from nesting inside anif;sentry.tsonly updates comments to mention both shapes.pipeline/tier2-report.test.mjsadds nine tests for envelope grouping, title stability, guards (success line, mid-line mention, other stderr kinds), and TS-first ordering when both patterns appear on one line.Reviewed by Cursor Bugbot for commit ee0701d. Bugbot is set up for automated code reviews on this repo. Configure here.