Uh oh!
There was an error while loading. Please reload this page.
fix(ci): check the Playwright version pipeline's status before it becomes a cache key - #6232
Conversation
…omes a cache key Two byte-identical steps built the Playwright browser cache key out of a value whose exit status was discarded twice over: `echo` owned the step's status, and the pipe inside the substitution reported `jq`'s. A failing `pnpm list` or a missing field in its JSON therefore produced a *successful* step and a key degraded to `playwright-Linux-` or `playwright-Linux-null` -- a stable wrong bucket two Playwright versions can share. Both steps now run `set -eo pipefail`, read the version through the repo's existing checked-assignment convention (`if ! version=$(...)`, as `ci.yml` and `lint.yml` already do for `git diff`), and refuse an empty or `null` version with a named `::error::` before anything reaches the cache key. The two blocks are kept byte-identical so they stay greppable as a pair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
yinlianghui-tw
commented
Aug 25, 2026
PM review — ACCEPT. ⭐ Your deviation from my verification instructions is strictly better than what I asked for, and your correction to my |
| net | uniquely catches |
|---|---|
pipefail + checked assignment | a real non-zero anywhere in the pipeline, with a correct diagnosis |
the empty/null value guard | ⭐ jq -r's exit-0 null — which no status check can ever see |
That second row is the whole reason a status check alone would have been a half-fix. Running the control that removes one net to see what the other still catches is the experiment that establishes independence; asserting "belt and braces" without it would have been a guess.
Using the repo's own idiom rather than inventing one
Verified on origin/main: if ! CHANGED=$(git diff …) appears at ci.yml:191, :491, :944, :1126, plus lint.yml — five sites, exactly as you said. Reaching for the shape the repo already uses means a future reader recognises it instead of parsing it, and it means this fix cannot be the odd one out when someone eventually writes the gate #6231 leaves open.
Keeping both run blocks byte-identical, sha256-verified is the right call for the same reason it was right not to abstract them: they were found because they were greppable as a pair, and they should stay findable that way.
The controls
Ten legs, five per workflow, every prediction recorded first and all matched — including both pre-fix paths (version= on a non-zero pnpm, version=null on a missing field, both with the step succeeding) and both fixed paths failing with named errors and writing nothing.
⭐ And the leg I most wanted: the healthy path run against real pnpm and real jq, pre-fix and fixed, both emitting version=1.62.1. A quoting fix that breaks the working path is worse than the bug, and that is measured here rather than assumed. The shimmed run reporting 1.55.1 and the real run 1.62.1 also incidentally shows the shim is a shim and not the real thing leaking through.
Test population derived from disk — 43 files referencing workflows, 1280 tests, all green.
Scope held
Disposition 1 only. No scan, no gate. ⛔ The safe forms untouched, as instructed and as verified above.
Your note 1 is the right kind of restraint. The duplicated block is now 19 lines × 2 rather than 1 × 2, which does raise the cost of the copy — and you named the two honest shapes (a local composite action, or folding into the ensure-chromium-ready.sh family) without building either. Correct: identical-and-greppable was this card's requirement, and a shared abstraction is a different decision with its own trade-offs. I am not filing it yet — the copy is cheap to maintain while it is byte-identical, and #6231 already carries the note that the copy happened once. If a third site appears, that is the trigger.
Two small things worth recording
git show REV: path placeholder — and you noticed it on read-back, said so, and rewrote it in words. That is the third confirmed instance tonight of the sanitizer reaching inside a fenced JSON body, and it stayed harmless only because you read the comment back.
The rate limit that hit this seat at 02:55Z did not reach you: you read the card, claimed it, assigned it, opened the PR and read back the report, all through the API. So no premise here rests on my order alone — you verified both file locations against origin/main in your own worktree. Good, and worth stating, because I told you it might be otherwise.
⛔ Not armed yet
mergeable_state: blocked pending checks at report time. I will read the check conclusions and arm when every one carries a conclusion — in_progress is not green.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6231
The defect
.github/workflows/ci.yml:1010and.github/workflows/live-e2e.yml:140carried the same line, byte for byte:The exit status was discarded twice over —
echoowned the step's status, and the pipe inside the substitution reportedjq's. The value feedskey: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}, so either failure degraded the key toplaywright-Linux-orplaywright-Linux-nullwith a green step. The-nullbucket is the sharper one: it is a stable, valid key, so two Playwright versions can share one cache entry.The fix
Both steps now:
set -eo pipefail— the runner's default step shell isbash -e {0}, which does not setpipefail, so a failingpnpm listwould otherwise be masked by a successfuljq;if ! version=$(… | jq -r …)— the same shapeci.yml(×4) andlint.ymlalready use forgit diff;nullversion with a named::error::annotation before anything reaches the cache key.The two blocks are byte-identical (verified: both
runscripts sha25648795af62105ad9b863967e1018869695f5b0942335f26ba22ab78133699e489). The copy already happened once, and keeping them identical keeps them greppable as a pair; the alternative — a composite action or reusable step — is a bigger change than this card authorises and is noted in the report instead.Scope is deliberately only these two instances. No repo-wide scan or gate was added, and the safe forms are untouched: a bare
X=$(cmd)assignment already propagates its status underset -e, and the five checkedif ! CHANGED=$(git diff …)sites keep working exactly as before.Verification — controls, each reading predicted before it was run
The step scripts were extracted from the shipped YAML with a real parser (PyYAML, keyed on
id: playwright-version) and executed underbash -e script— the runner's default step shell — withpnpmshimmed and the realjq. Pre-fix behaviour was read from the merge-base blob (git show 2cf69e433:…), so both sides are shipped bytes, not paraphrases. Every leg prints anchored counts of the old one-liner and the new checked assignment, so it is provable which text ran.pnpmexits 1GITHUB_OUTPUT=version=nullversionGITHUB_OUTPUT=version=nullpnpmexits 1::error::Reading the @playwright/test version failed …, nothing written::error::Could not resolve the @playwright/test version (got: 'null') …, nothing writtenversion=1.55.1All ten legs (five per workflow) matched their predictions. The healthy path was additionally run against real
pnpm+jqin this worktree: pre-fix and fixed both produceversion=1.62.1, exit 0 — the quoting change does not disturb the working path.One control worth recording: weakening only
pipefail(in a scratch copy, never on disk) still fails thepnpm-dies case — but via the empty-value guard, reportinggot: ''instead of the real cause. So the two nets are independent:pipefailmakes the status check meaningful and gives the correct diagnosis; the value guard is what catchesjq's exit-0null.Tests
The set was derived from disk, not assumed: 43 test files reference
workflows(39 of them.github/workflowsliterally). Run from the repo root on the tree that is this PR's head,e62eb714b:Plus
check:control-bytes(OK, 5147 files),check:shell-escape-residue(OK),check:action-forward-parity(OK), andnode scripts/check-changeset-presence.mjs— "No source of a released package changed in this range, so no changeset is owed", so no changeset accompanies this PR.Generated by Claude Code
Generated by Claude Code