fix(runner): give Tier-2 compile diagnostics a constant title and one bucket (DEV-2854) - #326
Merged
Merged
Conversation
…led bucket (DEV-2854)
DEV-2854 was rewritten today after both of its original premises were
checked against live Sentry data and withdrawn:
- These events are NOT the App.tsx:332 catch-all
(`Sentry.captureException(e, { tags: { context: "tier2-runtime" } } })`).
DEMOS-3K/3H event tags read `Type: default`, `kind: "stderr"`,
`level: "warning"` — a `captureMessage` from `reportDemoEvent`, reached
via `ContainerRuntime.onStderr`. Querying `context:tier2-runtime`
returns 4 unrelated, already-ignored `TypeError: Failed to fetch`
issues and zero compiler diagnostics. App.tsx:332 is untouched.
- Per-code fingerprinting is NOT a gap to close. `normalizeMonitorMessage`'s
quoted-string rule already collapses messages within one TS code
(DEMOS-3M holds six distinct quoted identifiers in one issue), so
keying a new fingerprint on the code would be a no-op.
What was actually broken:
1. Title flap: a fingerprint coarser than the message, with the raw
message still driving the title, means the issue title names
whichever sample arrived last (DEMOS-3K titled `',' expected` while
its newest event says `')' expected`).
2. Cross-code spread: one bad Angular-editing session mints 20+ distinct
TS codes, each its own single-event issue, because nothing grouped
across codes.
Fix: new import-free `tier2Report.ts` (mirrors `tier1Report.ts`) with an
allowlist recogniser (`/\bTS\d{4,5}:\s/`, code in diagnostic position —
a bare `TS1005` in prose does not match) that, when matched, returns a
flat fingerprint (`["demo-runtime","stderr","tier2-compile"]`, never
keyed on the code — unbounded vocabulary, ~20 issues per session
otherwise), a constant title ("Tier-2 compile failed", never
code-varying — a code-varying title on a flat fingerprint flaps exactly
like today's does), the code as a `ts_code` tag (omitted when a line
carries more than one distinct code), and the raw line in
`extra.compileDiagnostic`. Everything that doesn't match — NG8001/NG8002
(plausibly our own Angular starter's `HotTableModule` wiring),
`Failure reason:`, `::…::`, vite internals, `Could not resolve` — is
untouched by construction; the allowlist choice over a denylist is
deliberate and pinned by guard tests. `sentry.ts` wires this into
`reportDemoEvent` (tags/fingerprint/extra merge, plus the `display`
line); `demoRelayBudget.admit` deliberately keeps keying on the raw
message so 20 distinct diagnostics still cost 20 of
`MONITOR_EVENT_CEILING` — collapsing that key would drop every
diagnostic after the first and lose its `extra`.
Verified master's actual grouping behavior (built `packages/runtime`
export, unaffected by this change) rather than only asserting it:
normalizeMonitorMessage(TS1005) and normalizeMonitorMessage(TS2304)
already differ, so their fingerprints differ today (cross-code spread,
confirmed) and `display` was always the raw per-sample message (title
flap, confirmed).
Honest test limits: `pipeline/tier2-report.test.mjs` cannot "fail on
revert" in the strict sense — reverting deletes the module the test
imports, it doesn't restore old behavior for the test to exercise. And
the wiring inside `reportDemoEvent` stays untestable — `sentry.ts`
imports `@sentry/react` and reads `import.meta.env`, so `node --test`
cannot import it; no text-grep assertion was added to paper over that
gap.
Deviation from the plan text: the DEMOS-52-shaped fixture ("a TS2345
block repeated and truncated") is a single *distinct* code repeated
twice, not two distinct codes, so per the plan's own "single distinct
code" rule it DOES get a `ts_code` tag. Split into two fixtures instead:
TS2345_REPEATED (one distinct code, tag present) and a separate
TS1005+TS2304 line (two distinct codes, tag omitted).
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.
Sentry DEMOS-3K / 3M / 4F / 41 / 3H — Angular Tier-2 compile diagnostics. Triaged under DEV-2852.
The ticket asked for the wrong thing, in two ways
1. It named the wrong site. DEV-2854 was filed against the catch-all at
App.tsx:332. These events do not come from there. Their tags readType: default,kind: stderr,tier: 2,level: warning— that iscaptureMessageviareportDemoEvent, reached fromContainerRuntime.onStderr. Querying thecontext:tier2-runtimetag thatApp.tsx:332actually stamps returns 4 issues over 90d, allTypeError: Failed to fetch, all already ignored. That catch-all has never emitted a compiler diagnostic, and this PR does not touch it.2. The fix it asked for was already in place. The ticket wanted the fingerprint keyed on the TS diagnostic code so mid-typing keystrokes collapse per code. That collapse already happens, via
normalizeMonitorMessage's quoted-string rule: DEMOS-3M already holds six messages in one issue ('amount','hf','height','$B$4', and two Cyrillic identifiers), DEMOS-3K holds three. Keying onTS1005would have been a no-op.What is actually broken
Title flap — tier-1's second defect, live on this channel. A fingerprint coarser than the message with the raw message still in the title means the title names whichever sample arrived last. DEMOS-3K's title says
TS1005: ',' expectedwhile its newest event says')' expected. And DEMOS-5Q, on a release that already contains the tier-2 envelope fingerprint fix, is a permanent bucket titledApplication bundle generation failed. [0.505 seconds] - 2026-09-02T07:40:26.664Z— named after one timestamp, forever.Cross-code spread — one visitor session on 2026-08-25 minted 20+ distinct codes (TS1002, TS1005, TS1109, TS1135, TS1434, TS2304, TS2345, TS2554, TS2561, TS2695, TS4111, TS7006, TS18004 …), each its own single-event issue.
The fix
New import-free
apps/authoring/src/tier2Report.ts, called fromreportDemoEvent:/\bTS\d{4,5}:\s/— the code in diagnostic position. A bareTS1005in visitor prose does not match.["demo-runtime","stderr","tier2-compile"], matchingtier1Report.ts's flat["demo-runtime","sandpack-compile"]for precisely this population — bundler diagnostics over source an anonymous visitor is typing.ts_codetag, never part of the fingerprint. Per-code keying leaves ~20 issues per bad editing session and is unbounded in the TS vocabulary; a faceted tag is strictly more usable than 20 one-event issues."Tier-2 compile failed", raw line toextra.compileDiagnostic.causeCode()infailure-log.tsreturns"other"and is deliberately excluded from prose lines. This is what answers DEMOS-3H —Unexpected "}"has no TS code, is untouched, and needs nothing, because<str>already groups it withUnexpected ",".The invariant that shaped this: constant title iff flat fingerprint. That forecloses the tempting middle option
"Tier-2 compile failed (TS1005)"— a code-varying title on a flat fingerprint flaps exactly like today's bug does.frameworkandtierstay out of the fingerprint deliberately. They are already tags, and the house rule is that instrumentation facets sit beside the fingerprint, never inside it.kind: "stderr"only exists on the container engine, sotieris constant here anyway.The allowlist is the safety property
Because the recogniser is an allowlist rather than a denylist, these stay loud with their own titles by construction, not by a list someone must maintain:
NG8001/NG8002('hot-table' is not a known element,Can't bind to 'settings'— DEMOS-5T/5R/5S, plausibly our Angular starter'sHotTableModulewiring),Failure reason:(DEMOS-5B),::frozen install failed…::, vite/vue internal errors, andCould not resolve "./…". Four guard tests pin this; their job is to fail if anyone rewrites this as a denylist.Verification
pnpm test—1069 tests / 1067 pass / 0 fail / 2 todo(todos pre-existing)pnpm typecheck— clean across all 4 packages (this is what covers app code;pnpm buildat root builds packages only, notapps/authoring)pipeline/tier2-report.test.mjs— 12/12.tier1-report.test.mjs+monitor-inject.test.mjs— 73/73, unaffected.Two honest limits, stated rather than papered over. A pure-module test cannot fail-on-revert, because reverting deletes the module. Instead, master's actual grouping behaviour was measured against the built
packages/runtimeexport:normalizeMonitorMessage(TS1005)→"✘ [ERROR] TS1005: <str> expected. …"versusTS2304→"✘ [ERROR] TS2304: Cannot find name <str>. …"— different, so master's fingerprints genuinely differ per code anddisplay === messagealways. Both defects are empirically real, not inferred. (The numeric rule leaves the digits insideTS1005alone — no word boundary between the letters and the digits — which is why the code survives normalization while the rest of the message varies.)And the wiring inside
reportDemoEventhas no test coverage:sentry.tscannot be imported bynode --test(@sentry/react+import.meta.env). Same structural limitation the file already has forisUnhandledNoise. No text-grep assertion was added to fake coverage of it.One thing verified rather than assumed:
isUnhandledNoiseinspects onlyevent.exception?.valueswithmechanism.handled === false, andSentry.inithere does not setattachStacktrace, so acaptureMessageevent never populatesevent.exception. The new constant title cannot be accidentally swallowed by that gate.After deploy
DEMOS-3K/3M/4F/41 plus 3F, 3T, 3Y, 47, 49, 4A, 4X, 4Z, 50, 51, 52 stop receiving events and one new
Tier-2 compile failedbucket opens. They need bulk-resolving, same as the DEV-2853 ladder set.Relay budget is untouched on purpose: 20 distinct diagnostics still consume 20 of
MONITOR_EVENT_CEILING. Feeding the collapsed key intoadmitwould drop every diagnostic after the first and lose theirextraentirely.Follow-up, not here
A second recogniser for
/^Application bundle generation failed\./to give the already-flat envelope bucket a constant title — the DEMOS-5Q flap proven above. Distinct fingerprint from the diagnostic bucket: the envelope says the build failed, the diagnostic says why.🤖 Generated with Claude Code
Note
Low Risk
Observability-only change to Sentry fingerprinting/titles for a narrow stderr allowlist; demo relay budget and non-TS stderr reporting are unchanged.
Overview
Tier-2 TypeScript compiler
stderrlines from the demo monitor relay now group into a single Sentry issue with a stable title instead of one issue per diagnostic code and titles that flip with the last event.reportDemoEventcalls new import-freetier2StderrReport, which only matcheskind: "stderr"when aTS####:code appears in diagnostic position. Matched events use fingerprint["demo-runtime","stderr","tier2-compile"], display titleTier-2 compile failed, raw line inextra.compileDiagnostic, and optionalts_code/kind_class: tier2-compiletags. NG codes, resolve failures, and other stderr stay on the existing per-normalized-message path.Relay dedupe is unchanged:
demoRelayBudget.admitstill keys on the raw message so many distinct diagnostics in one session do not collapse and drop per-eventextra.pipeline/tier2-report.test.mjspins cross-code grouping, constant titles, allowlist guards, and multi-code tag behavior.Reviewed by Cursor Bugbot for commit 2401228. Bugbot is set up for automated code reviews on this repo. Configure here.