Skip to content

chore(runner): tag snapshot build failures with the pinned Handsontable build - #275

Merged
demtario merged 1 commit into
masterfrom
fix/sentry-demos-31-theme-css-specifier
Sep 1, 2026
Merged

chore(runner): tag snapshot build failures with the pinned Handsontable build#275
demtario merged 1 commit into
masterfrom
fix/sentry-demos-31-theme-css-specifier

Conversation

@demtario

@demtario demtario commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This fixes nothing, and that is the right outcome. The Sentry DEMOS-31 theme-CSS event ([commonjs--resolver] Missing "./styles/ht-theme-main.min.css" specifier in "handsontable" package) traces to an upstream pkg.pr.new exports-map gap in a Handsontable PR-preview build that has since been superseded — not to anything in this repo. This PR only adds diagnostics: the pinned Handsontable build ref and the framework are attached to BuildFailure and surfaced as Sentry tags, so a future recurrence is one search facet instead of an afternoon of tarball archaeology.

Why "not ours" is checkable, not asserted

Downloading and diffing pkg.pr.new tarballs directly: refs 13106/13150/13200 declare 217 exports with only 2 ./styles/* keys and no theme CSS entries at all, while refs 13220 and up declare 275-276 exports with all 18 ./styles/* keys, theme CSS included. The file itself is physically present in the broken tarballs (tar -tzf on 13106 shows package/styles/ht-theme-main.min.css in the archive) — only its exports declaration is missing. Upstream handsontable/package.json on develop has zero exports keys; the entire map is generated at release-build time, and that generation step evidently regressed for a window of PR-preview builds and was later corrected.

This isn't a spot check: all 113 published Handsontable versions across majors 15-19 (the full range DEFAULT_MIN_MAJOR/DEFAULT_MAX_MAJOR allows) and all 99 non-nightly prereleases were checked, and every one exports the theme CSS path. The only artifacts that ever lacked it are 0.0.0-next-* nightlies published before 2024-12-02, i.e. before Handsontable had themes at all — every nightly since has it, including everything current on 2026-08-20 when this event fired.

The event population is thin by design, not by omission: 2 events total on this issue, 1 user, same IP, 62 minutes apart on 2026-08-20, and no recurrence since.

Why a pre-build gate was rejected

A gate refusing "pkg.pr.new ref + a handsontable/styles/ht-theme-* import" would falsely 400 every current PR-pinned demo, since refs 13220 and up build fine with that same import. That's the same false-rejection trap PR #272's review caught with the peer-dependencies-only manifest check — a rejection of a build that would have succeeded is worse than the failure it prevents. The only way to decide the gate correctly would be to fetch the candidate build's own ~8.4 MB tarball and read its exports map on every create, inside a Worker. Not viable.

Why diagnostics are still worth it

The failing import is ours: runner/pipeline/blank-starters.mjs:83 emits import 'handsontable/styles/ht-theme-main.min.css'; unconditionally into every blank starter. So this isn't a one-off typo in an agent's manifest — any future regression in Handsontable's generated exports map re-fires this exact failure across every demo derived from a blank starter. And today's event carried no ht_version or framework tag at all, which is the entire reason this one report took manual tarball diffing to diagnose instead of a query.

What changed

  • BuildFailure gained two readonly fields, htRef: string | null and framework: string | null, threaded through an optional context param on the constructor and on describeBuildFailure (defaulted, so the existing two-arg callers in failure-log.test.mjs are unaffected).
  • runBuild computes { htRef: handsontableDependencyRef(files), framework: entry.framework } once per build, inside the try, and passes it to both throw sites (install and build failure). files is what the container actually installed — for a PR build the dependency value is the full pkg.pr.new URL, and handsontableDependencyRef parses it back to the bare id (e.g. "13106").
  • A new buildFailureTags(err) builds the Sentry tag object, omitting a key entirely when its value is unknown rather than emitting undefined or the string "null" — Sentry facets can't tell an absent key from a fabricated one, so getting this right matters.
  • index.ts now calls buildFailureTags(err) instead of the inline two-key tag literal. The fingerprint is untouched on purpose (["snapshot-build", err.phase, err.code], unchanged at index.ts:2003) — Handsontable version cardinality is unbounded (1453 nightlies alone), so putting htRef in the fingerprint would shard one issue into one-issue-per-build, which is the exact defect class PR fix(runner): give tier-2 build-failure envelopes one fingerprint (Sentry DEMOS-4G) #270 already fixed.
  • No behavioural surface: nothing is rejected, nothing is rewritten, no new failure path exists. This can only add tags to a report that already fires.

Tests

Three new tests in runner/pipeline/snapshot-build.test.mjs, each driving runBuild directly through a scripted sandbox (no route reaches runBuild in the test harness, since build_cache always hits):

  1. A pkg.pr.new-pinned manifest failing at the build step names the build (err.htRef === "13106") and the framework, and pins the exact tag object that reaches Sentry.
  2. A range-pinned manifest (^18.0.0, where handsontableDependencyRef correctly returns null) produces a tag object with no ht_version key at all — not "null", not undefined.
  3. An install-phase failure carries the same context as a build-phase one, proving it's attached at both throw sites.

Test 2 is the one worth double-checking, so it was mutation-tested rather than trusted: reverting buildFailureTags to tags.ht_version = String(err.htRef) passes test 1 but fails test 2 with ht_version: 'null' present; reverting to tags.ht_version = err.htRef ?? undefined also fails test 2, because assert.deepEqual (strict mode, via node:assert/strict) treats a present-but-undefined key as distinct from an absent one. Both plausible-but-wrong implementations are caught; only the correct one (if (err.htRef) tags.ht_version = err.htRef;) passes all three tests.

Baseline at master@6c1b5ecc, raw from runner/ (pnpm build && pnpm typecheck && pnpm test, no rtk): 933 tests / 931 pass / 0 fail / 2 todo, exit 0. After this change: 936 / 934 / 0 / 2, exit 0 — three new tests, nothing removed or changed elsewhere.

Known limitation, not fixed here

packages/runtime/src/version.ts's parsePkgPrNewFromUrl returns the URL segment after the last @ verbatim, with no length or shape check, and handsontableDependencyRef passes it through unchanged. Since the manifest can be MCP-agent-authored, a pathological "handsontable": "https://pkg.pr.new/handsontable@<very long string>" would land an arbitrarily long ht_version tag value. Sentry truncates rather than rejects, so this is noisy at worst, not exploitable, and it predates this change — fixing it would mean touching packages/runtime version parsing, which is out of scope for a diagnostics-only change to the build-failure report path. Noting it here so the next person doesn't have to rediscover it.

Out of scope

Rewriting the demo's theme import, aliasing it in a build config we don't own, or injecting a CDN stylesheet — all permanent workarounds for an upstream window that's already shut. Reporting the upstream Handsontable exports-map generation defect itself — real, not in this repo, and appears already corrected across the refs pkg.pr.new still serves.

Sentry: DEMOS-31 (https://handsoncode.sentry.io/issues/DEMOS-31)

🤖 Generated with Claude Code


Note

Low Risk
Observability-only changes on an existing error path; no new gates, API contracts, or build logic beyond richer Sentry tags.

Overview
Diagnostics-only for snapshot build failures (Sentry DEMOS-31): when install or build fails, the pinned Handsontable ref and framework are recorded on BuildFailure and sent to Sentry as optional ht_version / framework tags. Build behavior and error grouping are unchanged.

runBuild derives htRef from the manifest via handsontableDependencyRef(files) and threads that context through describeBuildFailure at both throw sites. New buildFailureTags omits tag keys when the ref is unknown (e.g. semver ranges) instead of emitting "null" or undefined. The API worker’s BuildFailure handler uses that helper for tags; the existing fingerprint stays ["snapshot-build", phase, code].

Three pipeline tests assert pkg.pr.new pins, range pins without ht_version, and install-phase context.

Reviewed by Cursor Bugbot for commit cd2d585. Bugbot is set up for automated code reviews on this repo. Configure here.

…le build

Investigation of the theme-CSS event on Sentry DEMOS-31 ("Missing
./styles/ht-theme-main.min.css specifier") found nothing to fix in this
repo. The event's failing exports lookup can only happen against an old
pkg.pr.new PR-preview build of Handsontable whose generated exports map
omitted every theme CSS subpath — refs 13106/13150/13200 (217 exports, 2
./styles/* keys) versus 13220 and up (275-276 exports, all 18, theme CSS
included). All 113 released Handsontable majors 15-19 and all 99
non-nightly prereleases were checked exhaustively and every one declares
the path; that pkg.pr.new packaging window is shut. This is an upstream
packaging defect in a superseded build, not a defect in our code, and no
pre-build gate can safely catch it (it would false-reject every current
PR-pinned demo that builds fine).

What is ours: the import itself is emitted unconditionally into every
blank starter by blank-starters.mjs, so a future regression in
Handsontable's generated exports map would re-fire this as a class. The
original event carried no ht_version or framework tag, which is why
diagnosing this one report took downloading and diffing five pkg.pr.new
tarballs by hand. This change attaches the pinned build ref (parsed back
from the installed package.json via handsontableDependencyRef) and the
framework to BuildFailure, and surfaces them as Sentry tags through a new
buildFailureTags() helper, so a recurrence is one search facet instead of
an afternoon of archaeology. The fingerprint is untouched on purpose:
version cardinality is unbounded, and fingerprinting on it would shard
the issue per build.

No behavioural change: nothing is rejected, nothing is rewritten, no new
failure path exists. Three tests in snapshot-build.test.mjs cover the tag
attachment, including the range-pinned case where ht_version must be
omitted from the tag object entirely rather than emitted as "null" or
undefined.

Sentry: DEMOS-31 (https://handsoncode.sentry.io/issues/DEMOS-31)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@demtario
demtario merged commit e614db5 into master Sep 1, 2026
6 checks passed
@demtario
demtario deleted the fix/sentry-demos-31-theme-css-specifier branch September 1, 2026 09:02
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.

1 participant