The gap
bash scripts/check.sh — the canonical "before you say it works" invocation, named as such in CONTRIBUTING.md and CLAUDE.md — runs no Playwright suite at all. It covers pytest, vitest, ruff, mypy, import-linter, eslint, and the four drift gates (openapi, generated client, wire fixtures, mcp tools). It does not start a browser.
There are three suites. Two of them are invisible to check.sh:
| Suite |
Command (from frontend/app) |
CI job |
| unit / drift |
bash scripts/check.sh (repo root) |
python, frontend |
| app + annotator e2e |
CI=1 npx playwright test |
annotator e2e (chromium) |
| full cycle, real server |
CI=1 npx playwright test -c playwright.cycle.config.ts |
browser cycle (chromium) |
Why this matters more than it sounds
During the 2026-08 audit remediation run (PRs #304–#313), the real-server cycle suite was three separate times the only suite to catch a regression:
In each case a reasonable person would have reported the work as verified on a green check.sh. One of them (#304) actually shipped that way and went red in CI.
Proposal
check.sh runs all three suites by default, or — if the ~4 minutes of browser time is judged too expensive for the common case — offers them via a flag and prints a loud, unmissable line when it skips them. Silence is the problem: "All checks passed" currently means "all the checks this script knows about", and nothing says which those are.
- Document the
CI=1 requirement in the script header and in CONTRIBUTING.md. playwright.config.ts sets reuseExistingServer: !process.env.CI, so a stale vite server on port 5273 answers instead of the build under test. During the run this produced six failures in unrelated demo scenarios that read as genuine code bugs and cost about fifteen minutes to diagnose.
- Consider a
check.sh browser subset alongside the existing python|frontend|generated subsets, so the browser suites are addressable by name.
Notes
annotator bench (chromium, manual) is workflow_dispatch-only and must stay out of any default — it is deliberately not a required check.
- The rule itself has been added to the
refactor-protocol skill's testing section, so an agent or a person reading it before a task now runs all three. This issue is about making the script honest rather than relying on the reader remembering.
The gap
bash scripts/check.sh— the canonical "before you say it works" invocation, named as such inCONTRIBUTING.mdandCLAUDE.md— runs no Playwright suite at all. It covers pytest, vitest, ruff, mypy, import-linter, eslint, and the four drift gates (openapi, generated client, wire fixtures, mcp tools). It does not start a browser.There are three suites. Two of them are invisible to
check.sh:frontend/app)bash scripts/check.sh(repo root)python,frontendCI=1 npx playwright testannotator e2e (chromium)CI=1 npx playwright test -c playwright.cycle.config.tsbrowser cycle (chromium)Why this matters more than it sounds
During the 2026-08 audit remediation run (PRs #304–#313), the real-server cycle suite was three separate times the only suite to catch a regression:
Finish jobstayed disabled over a job that was finished. The job'sallowed_actionswas never invalidated after a save, so the button kept the answer it got when every asset was stillunannotated.check.shwas green; the annotator e2e never drives a job to completion.counts.annotated, and accepting moves an asset out of that bucket. Unreachable until the review flow existed; the cycle run found it on the first pass, 3 of 3 becoming 2 of 3.In each case a reasonable person would have reported the work as verified on a green
check.sh. One of them (#304) actually shipped that way and went red in CI.Proposal
check.shruns all three suites by default, or — if the ~4 minutes of browser time is judged too expensive for the common case — offers them via a flag and prints a loud, unmissable line when it skips them. Silence is the problem: "All checks passed" currently means "all the checks this script knows about", and nothing says which those are.CI=1requirement in the script header and inCONTRIBUTING.md.playwright.config.tssetsreuseExistingServer: !process.env.CI, so a stale vite server on port 5273 answers instead of the build under test. During the run this produced six failures in unrelated demo scenarios that read as genuine code bugs and cost about fifteen minutes to diagnose.check.sh browsersubset alongside the existingpython|frontend|generatedsubsets, so the browser suites are addressable by name.Notes
annotator bench (chromium, manual)isworkflow_dispatch-only and must stay out of any default — it is deliberately not a required check.refactor-protocolskill's testing section, so an agent or a person reading it before a task now runs all three. This issue is about making the script honest rather than relying on the reader remembering.