Make a stopped apply schedule visible on a pull request (#23) - #47
Conversation
GitHub disables a scheduled workflow in a repository nobody has touched for 60 days, and does it silently -- no failed run, no notification, nothing on any pull request. A consumer whose weekly apply is switched off simply stops proposing releases to itself, and the only symptom is the absence of pull requests nobody was expecting on a particular day. That is the failure this mechanism exists to remove: before the apply, a repository lagged because a maintainer forgot to run sync.sh; with a dead schedule it lags because a workflow was switched off. Either way it sits on an old release, green, saying nothing. `check-drift.yml` runs on every pull request in every consumer and already reads the pin, so it resolves the latest release too and warns when the pin is behind it. A `::warning::` annotation, which appears on the pull request and in the run summary, and the check still passes. Failing would break pull requests that have nothing to do with the instructions, which is the thing pinning is for, and a check that cries wolf gets ignored -- including about the drift it was written to catch. Whether being several releases behind should eventually fail wants evidence about how long a repository actually lags, and the first two consumers were onboarded this month. Quiet in two cases, because a warning that is always there is furniture. A repository on the latest release sees nothing, and so does one where `shared-instructions/<latest release>` already exists: the apply pushed it, so the schedule ran, and an unmerged update pull request is a different situation this warning cannot help with. The branch rather than the pull request, though the pull request is what a reader would think of first -- listing pull requests needs `pull-requests: read`, a called workflow can only narrow the caller's token and never widen it, so thirteen consumers would each have to grant it by hand in the one directory nothing upstream may write. A branch is a ref, which `contents: read` already covers, and it answers the same question. The comparison is `scripts/check_drift.py`, driven from the workflow the way `pin_file.py` and `render.py` already are: every test here exercises a script and none exercises a workflow file, so logic inline in the YAML would have nowhere to be tested from.
The pipe-buffer cases handed every pin the same listing, and that listing carried `shared-instructions/0.3.0`. So the case named "behind" took the already-pushed path instead: two of the three cases ran the same branch of the script under two names, and the third branch -- the warning -- was never driven through a full pipe at all. That is the branch with something left to print after draining the listing, so it is the one where getting the drain wrong shows. A listing per case fixes it, and each case now also asserts which path it took, so the coverage cannot quietly drain away again behind a label that says it is there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Reviewer Round 1]
|
|
[Review Verdict Round 1: NOT_APPROVED] |
`git ls-remote` failing assigned an empty listing, which the script reads as "the apply pushed nothing" and warns about. An empty listing and an unreadable one are different things: the first rules the update branch out, the second rules nothing out, so a remote or authentication hiccup produced the warning on a repository whose apply had pushed the branch an hour ago -- the false positive the branch case exists to prevent. Pass `--branches-unknown` instead. The script stays quiet for that run, saying in the log which branch it could not rule out. The warning repeats on every pull request, so a skipped run loses nothing that does not come back. In the script rather than as an `exit 0` in the workflow, so the choice is exercised by `test_check_drift.py` -- including past a full pipe, since the quiet path is the one that never reads the listing it was handed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Author Round 1]
Verified locally with a stub |
|
[Reviewer Round 2]
No remaining findings. The updated implementation covers the issue’s three decision paths, keeps the warning non-blocking, names the required pin/latest/workflow action, and documents the behavior. |
|
[Review Verdict Round 2: APPROVED] |
Suggested squash commitTitle Body |
Summary
A consumer's scheduled apply can stop without anybody noticing — GitHub disables a scheduled workflow in a repository nobody has touched for 60 days, and does it silently. The only symptom is the absence of pull requests nobody was expecting on a particular day.
check-drift.ymlruns on every pull request in every consumer and already reads the pin, so it now resolves the latest release too and emits a::warning::annotation when the pin is behind. The check still passes: failing because a release exists upstream would break pull requests that have nothing to do with the instructions, which is what pinning is for, and a check that cries wolf gets ignored.It is silent in four cases, because a warning that is always there is furniture:
shared-instructions/<latest release>already exists — the apply pushed it, so the schedule ran, and an unmerged update pull request is a different situation. The branch rather than an open pull request, because listing pull requests needspull-requests: read, and a called workflow can only narrow the caller's token — every consumer'sci.ymlwould have to grant it by hand, in the one directory nothing upstream may write. A branch is a ref, whichcontents: readalready covers. The whole branch name, matched whole:git ls-remotematches its pattern at slash boundaries, so somebody'sfeature/shared-instructions/<latest>comes back in the listing and must not answer for the apply.--branches-unknownrather than as no branches — otherwise a remote hiccup warns the repository whose update branch is sitting right there. The warning repeats on every pull request, so the skipped run loses nothing permanent. An unresolvable latest release is the same: the step says so in the log and leaves the pull request alone.The comparison is
scripts/check_drift.py, driven from the workflow the waypin_file.pyandrender.pyalready are, since every test here exercises a script and none exercises a workflow file.scripts/test_check_drift.pycovers it, andci.ymlruns it.Files:
.github/workflows/check-drift.yml,.github/workflows/ci.yml,scripts/check_drift.py(new),scripts/test_check_drift.py(new),README.md,CHANGELOG.md.Closes #23
Test plan
shared-instructions/<latest release>branch sees nothingshared-instructions/*branch still gets warned, as does one holding afeature/shared-instructions/<latest>that only ends in the apply's branch name0.9.0is behind0.10.0,1.2and1.2.0are the same release, and the pre-schemev1is behindpython3 scripts/test_check_drift.pypasses, covering every outcomeci.ymlrunstest_check_drift.pypipefail— including on the quiet unknown-listing path, which never reads the listing it was handed — all leave the job green, andcontinue-on-errorcatches whatever else