ci: close three blind spots found investigating why CI missed sable-l10k - #221
Conversation
The assignment was to explain why 8 of 17 checks skipped on PR #220. The skip pattern is real, but it is not why the bug reached a customer. Testing that claim rather than assuming it is what turned up the rest. WOULD ANY EXISTING CHECK HAVE CAUGHT IT, IF IT HAD RUN? No. Checked out main at 0996492 (the buggy tree) and ran the entire suite against it: 2065 passed, and the only failures were three files that fail for environmental reasons here and are unrelated to polling. tests/scan-remote.test.ts and test_scan_remote.py both cover the poll loop — with the HTTP layer mocked, and neither ever injected a non-2xx mid-poll. They pass against the bug. And nothing executed github-action/action.yml at all, which is the file the customer's error came from. test-action.yml drives the ROOT action.yml, a different action that scans locally. Of the jobs in test-github-action.yml, two run hand-copied reimplementations of the action's bash (their own headers admit the duplication) and one greps the YAML as text. So the workflow that fires on github-action/** changes ran, and still executed none of the code. The gap was COVERAGE, and #220 closed it. The skip pattern would not have mattered. Three things found on the way there do: 1. publish-python had no `needs:`. publish-node has needed the test jobs since it was written; the Python half published to PyPI in parallel with the tests, ungated. A red suite blocked the npm release and shipped the PyPI one anyway — in a dual-implementation product where the two versions must match, that diverges them at the registry, the one place users cannot see it. Now gated. (publish.yaml also runs no pytest anywhere; filed separately.) 2. backend-api rendered identically whether it tested the backend or nothing. Its only real step is gated on RAFTER_API_KEY, which has never been set on this repo, so "backend-api ✓" has always meant "checked out and built". It now says so, loudly, in the log and the step summary. 3. test-node and test-python were skipped on internal PRs into main. On #220 — which changed both clients — neither ran. They now run on every PR. The premise that our own work is tested locally first is also weaker than it looks: this repo has test files that fail locally for environmental reasons, so "green on my machine" is not a signal anyone can act on. Cost is ~4 minutes of wall clock (234s and 100s, in parallel). The expensive part, the 6-way cross-platform grid with 3 macOS runners, stays gated — this reverses part of #219 narrowly and deliberately, not wholesale. Also established, not changed here: main has no branch protection at all. The only ruleset targets refs/heads/prod and contains no required-status-checks rule, so no check is required anywhere and a red PR can merge into main. That is a policy call, not a workflow fix.
|
Flagging the one part of this that reverses a deliberate decision, so it gets argued rather than slipped through. #219 gated the suite off for internal PRs into The premise cannot hold in this repo, because a local run is not a trustworthy signal. Three test files — Concretely: on #220 I ran everything locally and could not tell from that run whether I had broken anything. I only got a green I trusted by dispatching And on #220 specifically the gate meant that on a PR changing both the Node and the Python client, neither What I have not done is un-skip wholesale. #219's cost argument is sound and I took it seriously: the expensive part is the If the cost is still judged too high, reverting is a small, clean change: drop the |
python/tests/ executed in exactly one workflow in this repo — test-comprehensive.yml — and that workflow is not on the release path. publish.yaml had no Python test job at all, and validate-release.yml's test-build ran `python -m build` without pytest, so the pre-release gate asserted the Python package compiles and never that it works. A Python-only regression reached PyPI green, and PyPI is not somewhere you can quietly unship from. - publish.yaml: add test-python, mirroring test-comprehensive.yml's job. It runs in parallel with test-node, so it costs no wall clock the release was not already spending. - publish.yaml: gate BOTH publish jobs on it. The two registries publish from one push and version parity is enforced elsewhere, so a suite that goes red after npm has already published leaves the two runtimes at different versions on the two indexes. The divergence is the failure mode whichever half breaks. - publish.yaml: give test-node a Python toolchain. The node release path looked safe because publish-node has a `needs:` clause, but the job it needed was quietly weaker than the same job in test-comprehensive.yml: tests/cross-runtime-parity.test.ts gates its whole describe block on `python3 -c "import typer"` and describe.skip is silent, so all 40 parity assertions were skipped and the suite reported green. Verified by stubbing python3: "40 skipped", exit 0. Those tests are what enforce the dual-implementation contract — the ones a release least wants to skip. - validate-release.yml: run pytest in test-build. ~80s. Note for PR #221, which touches the same job: it adds `needs: [test-node, test-package]` to publish-python to stop a red NODE suite shipping the PyPI package. That is a different hole. Keep both sets of needs if the two land together. Co-authored-by: mayor <hello@rafter.so>
Closes sable-bm5k. The assignment was to explain why 8 of 17 checks skipped on PR #220. The skip pattern is real, but it is not why the bug reached a customer — and testing that rather than assuming it is what turned up the rest.
The sharp question: would any existing check have caught it, if it had run?
No. I checked out
mainat0996492— the buggy tree — and ran the entire suite against it:All seven failures are in three files (
cross-runtime-parity,skill-review-deep,skill-scanner) that fail here for environmental reasons and have nothing to do with polling.tests/scan-remote.test.tsandtest_scan_remote.pyboth cover the poll loop — with the HTTP layer mocked, and neither ever injected a non-2xx mid-poll. They pass against the bug.And nothing executed
github-action/action.ymlat all, which is the file the customer's error string came from:test-action.ymldrives the rootaction.yml— a different action that scans locally. Its four jobs never touch the composite action.test-github-action.yml,test-threshold-evaluationandtest-pr-comment-tiprun hand-copied reimplementations of the action's bash (both scripts' own headers admit the duplication is manual), andtest-action-yml-defaultsgreps the YAML as text.So the workflow that fires on
github-action/**changes did run, and still executed none of the code. The gap was coverage, and #220 closed it. The skip pattern would not have mattered.What does matter — three blind spots found on the way
1.
publish-pythonhad noneeds:.publish-nodehas needed the test jobs since it was written; the Python half published to PyPI in parallel with the tests, ungated. A red suite blocked the npm release and shipped the PyPI one anyway. In a dual-implementation product where CI enforces that the two versions match, that diverges them at the registry — the one place users can't see it. Now gated on[test-node, test-package].2.
backend-apirendered identically whether it tested the backend or nothing. Its only real step is gated onRAFTER_API_KEY, which has never been set on this repo (gh secret list:CLAWHUB_TOKEN,NPM_TOKEN,PYPI_TOKEN). Sobackend-api ✓has always meant "checked out and built". It now says so in the log and the step summary. Compounding it,tests/backend-api.test.tsself-skips on the same condition, so even when the step runs without a key it asserts nothing.3.
test-nodeandtest-pythonwere skipped on internal PRs intomain. On #220 — which changed both clients — neither ran. They now run on every PR.The premise behind the original gate ("our own work is reviewed and tested locally before it is pushed") is weaker than it looks: this repo has test files that fail locally for environmental reasons, so "green on my machine" isn't a signal anyone can act on. I only got a trustworthy green on #220 by dispatching the workflow manually.
Cost is ~4 minutes of wall clock —
test-node234s andtest-python100s, in parallel. The expensive part, the 6-waycross-platformgrid with 3 macOS runners, stays gated. This reverses part of #219 narrowly and deliberately; if you'd rather keep the old behavior, revert therun_coresplit and leave the other two fixes.Established, not changed here
mainhas no branch protection at all. The only ruleset (Prod Protection) targetsrefs/heads/prodand contains no required-status-checks rule. So no check is required anywhere, and a red PR can merge intomain. That also makes the "does a skipping job satisfy a required check" question moot today — nothing is required. Worth knowing before adding required checks: a job skipped by anif:gate reports as satisfied, so requiring these checks would not have closed this hole either.That's a policy call rather than a workflow fix, so it's flagged, not touched.
Verification
This PR is itself the test for change 3: it is an internal PR into
main, so under the old gatetest-nodeandtest-pythonwould both skip. They should now run, whilecross-platformstays skipped.