Skip to content

fix(runner): give the Tier-2 build-failure envelope a constant title (DEV-2876) - #333

Merged
demtario merged 1 commit into
masterfrom
fix/DEV-2876-tier2-build-envelope-title
Sep 9, 2026
Merged

fix(runner): give the Tier-2 build-failure envelope a constant title (DEV-2876)#333
demtario merged 1 commit into
masterfrom
fix/DEV-2876-tier2-build-envelope-title

Conversation

@demtario

@demtario demtario commented Sep 9, 2026

Copy link
Copy Markdown
Member

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, via normalizeMonitorMessage'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:

  • DEMOS-5QApplication bundle generation failed. [0.505 seconds] - 2026-09-02T07:40:26.664Z, on release 2472da96, which does contain the fingerprint fix. So the collapse is live and the title flap is what remains.
  • Same shape on DEMOS-53 [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 in extra.buildFailure.

Two distinct fingerprints, deliberately. The envelope says that the build failed; a TS diagnostic says why. ["demo-runtime","stderr","tier2-build"] versus tier2-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 \. excludes Application 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-null population. 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 in MONITOR_KINDS, so a forged postMessage from 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 keeps ts_code as a facet — and there is a test for it rather than a dismissal.

Duration and timestamp: extra verbatim, no tag

Nothing dropped, nothing faceted. A tag has to earn its cardinality, and ts_code earned 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 in extra because it is recoverable context, kept out of title and fingerprint because it is not a fault-class discriminator.

Distinct extra key (buildFailure, not compileDiagnostic) so extra stays 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, and Failure reason: — the parked DEMOS-5B, which appears nowhere in this repo and is deliberately left alone.

Verification

  • pipeline/tier2-report.test.mjs21/21 (12 pre-existing + 9 new)
  • pnpm test1148 tests / 1146 pass / 0 fail / 2 todo (todos pre-existing)
  • pnpm typecheck — clean across all four projects
  • node scripts/check-test-presence.mjs master — pass

Fail-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) returns null on 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 return object was restructured into a nested if, so this was the real risk — if any value drifted, the live tier2-compile bucket regroups and #326's work from earlier today silently comes undone. Independently diffed during review: fingerprint array, tags (including the singleCode spread), extra.compileDiagnostic and display are byte-identical to master; only indentation changed. All 12 pre-existing tests stayed green throughout.

sentry.ts is 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 the level map that sends stderrwarning), 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 a relayStderr property, not this module's, and the envelope never carried a cause before either.

Relay budget untouched on purpose: demoRelayBudget.admit still keys on the raw message, so an envelope plus a TS line still cost two of MONITOR_EVENT_CEILING. And normalizeMonitorMessage's ISO rule stays load-bearing for the stderrSeen dedupe in relayStderr even 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 fingerprint tier2-build, constant title Tier-2 build failed, and the full raw line in extra.buildFailure instead of the issue title.

tier2StderrReport is refactored into two allowlisted branches (TS diagnostic first, then envelope) with separate fingerprints from tier2-compile so “build failed” and “why it failed” do not merge. Compile-path behaviour is unchanged aside from nesting inside an if; sentry.ts only updates comments to mention both shapes.

pipeline/tier2-report.test.mjs adds 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.

…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>
@demtario demtario self-assigned this Sep 9, 2026
@demtario
demtario merged commit 023a236 into master Sep 9, 2026
6 checks passed
@demtario
demtario deleted the fix/DEV-2876-tier2-build-envelope-title branch September 9, 2026 11:20
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