chore(runner): tag snapshot build failures with the pinned Handsontable build - #275
Merged
Merged
Conversation
…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>
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.
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 toBuildFailureand 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/13200declare 217 exports with only 2./styles/*keys and no theme CSS entries at all, while refs13220and up declare 275-276 exports with all 18./styles/*keys, theme CSS included. The file itself is physically present in the broken tarballs (tar -tzfon13106showspackage/styles/ht-theme-main.min.cssin the archive) — only itsexportsdeclaration is missing. Upstreamhandsontable/package.jsonondevelophas zeroexportskeys; 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_MAJORallows) and all 99 non-nightly prereleases were checked, and every one exports the theme CSS path. The only artifacts that ever lacked it are0.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 refs13220and 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:83emitsimport '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 noht_versionorframeworktag at all, which is the entire reason this one report took manual tarball diffing to diagnose instead of a query.What changed
BuildFailuregained two readonly fields,htRef: string | nullandframework: string | null, threaded through an optionalcontextparam on the constructor and ondescribeBuildFailure(defaulted, so the existing two-arg callers infailure-log.test.mjsare unaffected).runBuildcomputes{ htRef: handsontableDependencyRef(files), framework: entry.framework }once per build, inside thetry, and passes it to both throw sites (install and build failure).filesis what the container actually installed — for a PR build the dependency value is the fullpkg.pr.newURL, andhandsontableDependencyRefparses it back to the bare id (e.g."13106").buildFailureTags(err)builds the Sentry tag object, omitting a key entirely when its value is unknown rather than emittingundefinedor the string"null"— Sentry facets can't tell an absent key from a fabricated one, so getting this right matters.index.tsnow callsbuildFailureTags(err)instead of the inline two-key tag literal. The fingerprint is untouched on purpose (["snapshot-build", err.phase, err.code], unchanged atindex.ts:2003) — Handsontable version cardinality is unbounded (1453 nightlies alone), so puttinghtRefin 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.Tests
Three new tests in
runner/pipeline/snapshot-build.test.mjs, each drivingrunBuilddirectly through a scripted sandbox (no route reachesrunBuildin the test harness, sincebuild_cachealways hits):err.htRef === "13106") and the framework, and pins the exact tag object that reaches Sentry.^18.0.0, wherehandsontableDependencyRefcorrectly returnsnull) produces a tag object with noht_versionkey at all — not"null", notundefined.Test 2 is the one worth double-checking, so it was mutation-tested rather than trusted: reverting
buildFailureTagstotags.ht_version = String(err.htRef)passes test 1 but fails test 2 withht_version: 'null'present; reverting totags.ht_version = err.htRef ?? undefinedalso fails test 2, becauseassert.deepEqual(strict mode, vianode:assert/strict) treats a present-but-undefinedkey 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 fromrunner/(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'sparsePkgPrNewFromUrlreturns the URL segment after the last@verbatim, with no length or shape check, andhandsontableDependencyRefpasses 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 longht_versiontag value. Sentry truncates rather than rejects, so this is noisy at worst, not exploitable, and it predates this change — fixing it would mean touchingpackages/runtimeversion 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
BuildFailureand sent to Sentry as optionalht_version/frameworktags. Build behavior and error grouping are unchanged.runBuildderiveshtReffrom the manifest viahandsontableDependencyRef(files)and threads that context throughdescribeBuildFailureat both throw sites. NewbuildFailureTagsomits tag keys when the ref is unknown (e.g. semver ranges) instead of emitting"null"orundefined. The API worker’sBuildFailurehandler 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.