The only sanctioned way to re-accept many goldens at once (after a renderer, font, token or Jest
upgrade, or an intended redesign): the real assertions are green first, a triage names the affected
set, each file is re-accepted alone, every changed golden is looked at and recorded, orphans go, and
the whole golden project passes twice without -u. rebaseline.mjs runs each step and writes the
evidence; check-rebaseline.mjs proves it. The developer approves twice.
Rules that must hold
- Re-accept only for a named cause, after a full triage.
--start --reason "<cause>", then the whole golden project without-u(triage-first); every failure must be what that cause predicts. Two unrelated causes are two rebaselines. Why: a golden that moved for another reason is a bug, and the triage is the only plan. - The real assertions are green first. Every Jest project except the golden one, complete and
green, before the first update (
assertions-first). Why: a golden asserts nothing about correctness; re-accepting over a red unit suite freezes the bug. - Nothing but goldens changes once the rebaseline starts, and the install is the lockfile. Code,
tests, configs, the lockfile and frozen golden paths keep the hash
--startrecorded (source-changed);node_modulesequalspackage-lock.json(install-matches-lock). Why: a fix made mid-way hides inside the re-accepted output, and a stale install writes goldens CI cannot reproduce. - One file per
-urun, path first, previewed, by hand.jest <file> --selectProjects golden --ci -uafter--listTestslists only that file (update-one-file). Never-uin package.json scripts, git or agent hooks, or CI; only this skill's runner, invoked by hand once per planned file, runs it. Why: after--selectProjectsa path is read as a project name and every golden is rewritten (verified). - Only files the triage shows failing or holding obsolete entries are updated (
update-unplanned). Why: updating anything else is how unrelated drift slips in under the cause. - Goldens change only through a logged update run. No hand edit of a
.snap*or PNG; only orphans are deleted by hand (golden-changed-unlogged). A header-only rewrite after a Jest major is the one exception the checker recognises. Why: a hand edit records output the code does not produce. - Every changed golden is inspected and recorded. Read the
.snapdiff line by line; open each triage diff composite and each new PNG; oneinspection.jsonentry per changed path with verdictintendedororphanand a note of at least 15 characters (inspection-record). Anything less than a clear yes means stop. Why: the look is the whole review; an unread golden freezes a bug. - Contract entries are never re-accepted. A failing
[contract]assertion stops the ritual: stop and ask the reviewer; the runner refuses to update such a file and the checker compares every contract entry with its recorded hash (contract-entry-changed). Why: installed versions and kept files still expect the old output. - Tolerances are out of scope. The pixel matcher and
contracts/goldens.jsonstay byte for byte (tolerance-changed); a raise goes through the reviewer in its own commit (golden-and-snapshot-tests). Why: a raised tolerance silently accepts every future regression below it. - Orphans are deleted by hand, in the same change, before the proof. No snapshot outlives its
test, no PNG outlives the test that claims it (
orphan-golden). Why: Jest never reports a.snap.iosorphan, and jest-image-snapshot only deletes unused images through its opt-in outdated-snapshot reporter, which deletes without a record and, on a one-file run, deletes other tests' live baselines (verified); soJEST_IMAGE_SNAPSHOT_TRACK_OBSOLETEstays unset for the whole ritual (--updaterefuses while it is set). - Proven twice without the flag, over the whole golden project (
proof-without-flag):--ci, no-u, green, nothing written, no obsolete entry, every golden test ran, and no golden changed after it (proof-goldens.json). Why: the first run proves the new goldens reproduce, the second catches output that changes between runs. - Stop and ask twice, then one dedicated commit. Before the first update (show the plan) and
before the commit (show the record), wait for the developer's go-ahead. The commit holds the cause
and the goldens only, names every re-accepted test and deleted orphan, and ends with a
Gate-Change:trailer (rebaseline-message). Why: the agent that wrote the change should not be the only reviewer of its goldens, and the history must say what moved and why.
Workflow
- Decide it is a rebaseline. Read "When a rebaseline is the right tool" in references/rebaseline-ritual.md. One golden moving with your change is golden-and-snapshot-tests' one-file flow; a contract entry or a tolerance is never this.
- Prepare ("Before you start"): the cause final in the tree, unrelated work committed or stashed,
goldens committed,
npm ci,JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETEunset. - Start:
node ${CLAUDE_SKILL_DIR}/scripts/rebaseline.mjs . --start --reason "<cause>"(add--dry-runfirst to see the checks without writing). It refuses a stale install, uncommitted goldens or an orphan snapshot holding[contract]entries (settle that with the reviewer first), and lists orphans already present; fix and rerun. - Assertions:
node ${CLAUDE_SKILL_DIR}/scripts/rebaseline.mjs . --assertions --max-workers 2. Red means stop: fix the code, then--start --restart. - Triage:
node ${CLAUDE_SKILL_DIR}/scripts/rebaseline.mjs . --triage. Read every failure and open every composite it lists underreports/rebaseline/triage-diffs/(left baseline, red differences, right new render). Decide each with the triage table in the ritual reference. - Stop and ask the developer before the first update: show the plan (examples/approval-requests.md, part 1) and wait for the go-ahead.
- Update each planned file alone:
node ${CLAUDE_SKILL_DIR}/scripts/rebaseline.mjs . --update <file>. It previews with--listTests, runs--ci -uon that file and lists what changed. - Inspect and record:
git diff -- <snapshot>line by line; open each new PNG next to its composite; fill eachtodoentry inreports/rebaseline/inspection.json(examples/inspection.json). Not a clear yes: revert that path, stop, fix. - Delete orphans:
git rmeach snapshot whose test is gone and each PNG no test claims; record them with verdictorphan. - Prove:
node ${CLAUDE_SKILL_DIR}/scripts/rebaseline.mjs . --proof(two runs; it records the goldens it ran against, so any golden touched afterwards means another--proof). - Check:
node ${CLAUDE_SKILL_DIR}/scripts/check-rebaseline.mjs .; fix every FAIL (file, rule, fix) and rerun untilRESULT: PASS. Rules, options and limits: references/evidence-and-checker.md; the Jest behaviour behind them: references/jest-facts.md. - Stop and ask the developer before the commit: show the record and the PNGs (part 2 of the approval example) and wait for approval.
- Commit: write
reports/rebaseline/commit-message.txt(examples/rebaseline-commit.txt), runnode ${CLAUDE_SKILL_DIR}/scripts/check-rebaseline.mjs . --message reports/rebaseline/commit-message.txt, thengit commit -F reports/rebaseline/commit-message.txt; deletereports/rebaseline/.
Definition of done
- The cause is named in
before.jsonand every re-accepted golden is explained by it. - The developer approved the plan before the first update and the record before the commit.
- No
[contract]entry, pixel matcher orcontracts/goldens.jsonchanged; no tolerance moved. - Every changed golden has an
intendedororphanentry with a real note; orphans are deleted. - One commit holds the cause and the goldens, names every re-accepted file, ends with
Gate-Change:. -
node ${CLAUDE_SKILL_DIR}/scripts/check-rebaseline.mjs . --message reports/rebaseline/commit-message.txtprintsRESULT: PASS(no SKIP lines are expected)
Anti-patterns
npx jest -uto make a red suite green. It rewrites every differing golden, bugs included. Read the failures, fix the code, re-accept only the planned files.npx jest --selectProjects golden <file> -u. The path becomes a project name and every golden is rewritten. Put the path first; let--updatepreview it.- Following Jest's "To remove it, run
npm test -- -u". That updates the whole project. Use--update <file>on the file with the obsolete entry. - Fixing a bug found during the rebaseline and carrying on. The fix is then hidden in re-accepted
output. Revert the goldens, fix,
--start --restart. - Re-accepting a subset of the failures "for now". The rest fail on the next run and get accepted unread later. Plan the whole affected set, or fix what is not the cause.
- Reading the diffstat instead of the diffs. "12 PNGs changed" says nothing. Open each composite and each new image; an unrelated surface that moved is what the ritual exists to catch.
- Raising
failureThresholdbecause the upgrade is noisy. CanvasKit renders are deterministic; a tolerance raise needs the reviewer and its own commit. - Running the ritual in CI or a hook. A gate that rewrites what it checks asserts nothing. CI runs
jest --ciand only proves. - Committing the evidence or a diff image.
reports/is git-ignored; the record lives in the commit message and the pull request.
Files in this skill
| File | What it is | Read/run when |
|---|---|---|
| references/rebaseline-ritual.md | When to rebaseline, preparation, every step and why, reading data and pixel diffs, triage table, the two stop-and-ask moments, the commit, Jest majors, renamed tests, Skia upgrades, screenshot baselines, iOS and Android, multi-package | Workflow steps 1, 2, 5 and 13 |
| references/jest-facts.md | Verified Jest 29.7 / jest-expo 57 / jest-image-snapshot 6.5.2 behaviour: path order, --ci -u, obsolete entries, orphans, image updates, the --json result, Jest majors, with re-verify commands |
When a Jest fact matters or after a Jest upgrade |
| references/evidence-and-checker.md | The evidence folder, before.json and inspection.json formats, the runner's modes and refusals, the checker's rules, config keys, expected output, limits, troubleshooting |
Workflow steps 3 to 11 |
| examples/inspection.json | A filled inspection record for one cause, a Skia upgrade (pixel goldens, an orphan) | Workflow step 8 |
| examples/approval-requests.md | What to show the developer before the first update and before the commit (the same Skia upgrade) | Workflow steps 6 and 12 |
| examples/rebaseline-commit.txt | Rebaseline commit messages (the same Skia upgrade, a Jest-major header rewrite) | Workflow step 13 |
scripts/rebaseline.mjs |
Runs one step (--start, --assertions, --triage, --update <file>, --proof) and writes its evidence; refuses out-of-order or unsafe steps |
Workflow steps 3 to 10 |
scripts/check-rebaseline.mjs |
Proves the ritual from the evidence and the tree (13 rules), and with --message the commit message |
Workflow steps 11 and 13, the definition of done |
scripts/lib/rebaseline-lib.mjs |
Shared logic: layout and config, inventory and hashes, snapshot parsing, image claims, Jest results, lockfile comparison | When a script changes |
scripts/selftest.mjs |
Proves both scripts against the fixtures (the bases are built with the real --start) |
After changing a script or a fixture |
scripts/check-lib.mjs |
Shared script library (synced; do not edit here) | Never by hand |
assets/shared.json |
Declares the synced check-lib | When adding a shared file |
tests/bases/ |
before/ (a small Tasks app with data and pixel goldens), after/ (the re-accepted state), evidence/ (Jest results and the record), runner/ (a fake Jest for the runner suite) |
When the fixture base changes |
tests/fixtures/ |
Overlay suites for both scripts: good/, one or more bad-* per rule, pass-* and error-* cases; a case's ENV.txt sets an environment variable for that case only |
When adding a rule or a refusal |
Related skills
golden-and-snapshot-tests- the golden policy, the one-file flow, contracts, tolerances and the history checker for the commit.unit-and-component-tests- the Jest projects and the assertions that must be green first.e2e-maestro- simulator and emulator screenshot baselines and their compare run.skia-canvas-rendering- the drawn surfaces whose pixel goldens move with a renderer upgrade.design-system-structure- token changes that move drawn surfaces.dependency-management- the dependency bump that is often the cause, andnpm ci.expo-sdk-upgrade- SDK moves that bring Jest majors and renderer upgrades.seeded-determinism-and-golden-vectors- golden vectors, which are never re-accepted.git-commits-and-reporting- the commit format and the Gate-Change trailer.quality-gates- gated paths and the commit-msg hook that asks for the trailer.ci-pipeline-and-gates- CI runsjest --ciand only proves goldens.