Skip to content

fix(runner): give Tier-2 compile diagnostics a constant title and one bucket (DEV-2854) - #326

Merged
demtario merged 1 commit into
masterfrom
fix/DEV-2854-tier2-compile-fingerprint
Sep 9, 2026
Merged

fix(runner): give Tier-2 compile diagnostics a constant title and one bucket (DEV-2854)#326
demtario merged 1 commit into
masterfrom
fix/DEV-2854-tier2-compile-fingerprint

Conversation

@demtario

@demtario demtario commented Sep 9, 2026

Copy link
Copy Markdown
Member

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 read Type: default, kind: stderr, tier: 2, level: warning — that is captureMessage via reportDemoEvent, reached from ContainerRuntime.onStderr. Querying the context:tier2-runtime tag that App.tsx:332 actually stamps returns 4 issues over 90d, all TypeError: 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 on TS1005 would 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: ',' expected while its newest event says ')' expected. And DEMOS-5Q, on a release that already contains the tier-2 envelope fingerprint fix, is a permanent bucket titled Application 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 from reportDemoEvent:

  • Recogniser: /\bTS\d{4,5}:\s/ — the code in diagnostic position. A bare TS1005 in visitor prose does not match.
  • Flat fingerprint ["demo-runtime","stderr","tier2-compile"], matching tier1Report.ts's flat ["demo-runtime","sandpack-compile"] for precisely this population — bundler diagnostics over source an anonymous visitor is typing.
  • The code becomes a ts_code tag, 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.
  • Constant title "Tier-2 compile failed", raw line to extra.compileDiagnostic.
  • No recognisable code → nothing changes. The event keeps today's normalized-message fingerprint and its own title. Not reclassified, not merged, no synthetic title. Precedent: causeCode() in failure-log.ts returns "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 with Unexpected ",".
  • Multi-code lines: first match wins for the tag; if a line carries more than one distinct code, the tag is omitted rather than picked arbitrarily.

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.

framework and tier stay 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, so tier is 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's HotTableModule wiring), Failure reason: (DEMOS-5B), ::frozen install failed…::, vite/vue internal errors, and Could not resolve "./…". Four guard tests pin this; their job is to fail if anyone rewrites this as a denylist.

Verification

  • pnpm test1069 tests / 1067 pass / 0 fail / 2 todo (todos pre-existing)
  • pnpm typecheck — clean across all 4 packages (this is what covers app code; pnpm build at root builds packages only, not apps/authoring)
  • New 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/runtime export: normalizeMonitorMessage(TS1005)"✘ [ERROR] TS1005: <str> expected. …" versus TS2304"✘ [ERROR] TS2304: Cannot find name <str>. …" — different, so master's fingerprints genuinely differ per code and display === message always. Both defects are empirically real, not inferred. (The numeric rule leaves the digits inside TS1005 alone — 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 reportDemoEvent has no test coverage: sentry.ts cannot be imported by node --test (@sentry/react + import.meta.env). Same structural limitation the file already has for isUnhandledNoise. No text-grep assertion was added to fake coverage of it.

One thing verified rather than assumed: isUnhandledNoise inspects only event.exception?.values with mechanism.handled === false, and Sentry.init here does not set attachStacktrace, so a captureMessage event never populates event.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 failed bucket 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 into admit would drop every diagnostic after the first and lose their extra entirely.

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 stderr lines 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.

reportDemoEvent calls new import-free tier2StderrReport, which only matches kind: "stderr" when a TS####: code appears in diagnostic position. Matched events use fingerprint ["demo-runtime","stderr","tier2-compile"], display title Tier-2 compile failed, raw line in extra.compileDiagnostic, and optional ts_code / kind_class: tier2-compile tags. NG codes, resolve failures, and other stderr stay on the existing per-normalized-message path.

Relay dedupe is unchanged: demoRelayBudget.admit still keys on the raw message so many distinct diagnostics in one session do not collapse and drop per-event extra.

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

…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>
@demtario demtario self-assigned this Sep 9, 2026
@demtario
demtario merged commit 9d045d8 into master Sep 9, 2026
6 checks passed
@demtario
demtario deleted the fix/DEV-2854-tier2-compile-fingerprint branch September 9, 2026 09:36
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