A verify target that rewrote the tree: four review points on a cri-tools script change
make verify-docs and make verify-go-modules in cri-tools rewrote files in your working tree, then failed if git status showed anything dirty. I changed them so they only check (PR #2212, merged 2026-10-16). The PR fixes issue #2197, which @SergeyKanzhelev filed, and it follows the plan @saschagrunert wrote in a comment there. @saschagrunert then reviewed the PR and raised four points. All four changed the merged scripts.
What verify did before
The old hack/verify-go-modules.sh ran the commands that fix the tree, then asked git whether anything was left over:
go mod tidy && go mod vendor && go mod verify
# Test if we can resolve all go modules
go list -mod=readonly -m all
STATUS=$(git status --porcelain)
if [[ -z $STATUS ]]; then
echo "tree is clean"
else
echo "tree is dirty, please commit all changes"
echo ""
echo "$STATUS"
exit 1
fi
verify-docs.sh had the same shape with go-md2man and docs/crictl.1. So a verify run edited your files, and it could not pass while you had uncommitted work. The issue puts it this way: “verify commands are both disruptive (override in-place) and cannot run on not-committed files.”
The plan I was given
@saschagrunert’s comment on the issue chose a narrow version of the third option in the issue. Build into a temp location and diff against the tree, without copying the tree. For modules that means go mod tidy -diff, go mod vendor -o "$tmp" plus a diff against vendor, and go mod verify. For docs it means running the doc tool into a temp file. Then add a make docs target next to make vendor, so that “verify-* only checks and docs/vendor write files”.
The comment also warned that a bare entry in .gitignore matched a vendored file, so that file was not in git and a plain diff -r against vendor/ would report it. The PR anchors that entry with a leading slash. The comment suggested committing the vendored file as well. At the version in go.mod the dependency ships no such file, so there was nothing to commit, and the anchor only stops a later bump from dropping one silently.
I wrote the PR to that plan. The tree stayed untouched, and in the PR description I wrote that I had checked a clean tree passes, that an edited docs/crictl.md, an untidy go.mod and an edited vendor/modules.txt each fail with a hint, and that the tree is byte-identical after every run. The description lists no run from another directory and none with an ignored file in the tree.
What review changed
The review was on commit b8c6f16. The PR head that was merged is 9d7078f; the PR is still one commit.
-
Run from anywhere. My
verify-go-modules.shrandiff -ru vendor "$TMP_DIR". @saschagrunert pointed out thatvendoris relative to the current directory, socd hack && ./verify-go-modules.shfails withdiff: vendor: No such file or directoryand a misleading “run make vendor” hint. The oldgit statuscheck worked from any subdirectory, so I had made it worse. Myverify-docs.shalready had the fix, and I had not copied it into this script. The review comment carried a suggestion block, and the merged script has exactly those lines:# Root of the repository REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${REPO_ROOT}" -
Compare what git will commit. This comment is the one I learned most from. A diff against
vendor/compares the files on disk, including files git ignores. @saschagrunert listed where that bites: a dependency bump can vendor a file matched by a root rule (*.iml,*~,.*/,release-notes.md) or by one of the 28 vendored.gitignorefiles. Their examples were otel ignoringgen/, wazero ignoringbuildanddist, and logrus ignoringvendor. Thenmake vendorwrites the file and it never gets committed. In the reviewer’s words: “The check then passes locally and fails in CI with a hint thatmake vendorcan’t fix.” Anchoring the one rule the plan comment named covers one instance. A root!/vendor/**negation would not cover the rest, because the vendored.gitignorefiles take precedence. The reviewer’s answer was a check after the diff, and the merged script contains it as written:ignored="$(cd "$TMP_DIR" && find . -type f | sed 's|^\./|vendor/|' | git -C "$REPO_ROOT" check-ignore --no-index --stdin || true)" if [[ -n $ignored ]]; then echo "vendored files are gitignored and would not be committed:" >&2 echo "$ignored" >&2 exit 1 fiI did not have this idea. It is @saschagrunert’s.
-
Do not bury the hint. On vendor drift,
diff -ruprints a unified diff of every changed vendored file. @saschagrunert measured it: bumpinggolang.org/x/netwithout re-vendoring gives about 1000 lines withdiff -ruand 9 withdiff -rq. “Since the fix is alwaysmake vendor, file names should be enough.” The merged script usesdiff -rq.verify-docs.shstill usesdiff -u, because it compares one file. -
One copy of the doc-tool call. In my first version the Makefile
docstarget andverify-docs.sheach had their owngo run -C hack/tools github.com/cpuguy83/go-md2man/v2 ...line. The review said defining it once keeps the two from drifting, and offered two ways: a variable, or “having the script take the output path andmake docscall it”. I took the second. The merged Makefile hasCRICTL_MAN ?= $(CURDIR)/docs/crictl.1, and the script runs:make -s docs CRICTL_MAN="$TMP_DIR/crictl.1"
@saschagrunert approved with “Thanks!” and the merge bot merged the PR the same day.
Where the review found what I had not
None of the four points is covered by the checks the description lists. Each of those asks “does this fail when it should”. The review asked other questions. Where is the script run from? What will CI see that my disk does not? How much output does a failure produce? Is the doc-tool call written down once?
The second question is the hard one. A verify script that reads the working tree answers “is this tree consistent”, and CI needs the answer “is this commit consistent”. Those differ whenever the working tree holds anything git ignores. The same split shows up in tests. A test that reads the machine it runs on, such as a global git config, passes on the machine where it was written and fails on another. I met that in a git-stats change for oh-my-openagent, where the CI run on Windows failed on a config path.
Before a PR that touches a verify script
- Run it from the repo root, from a subdirectory, and once with an unrelated dirty file. It must behave the same in all three.
- Hash or
git statusthe tree before and after a run. A verify script that changes anything has failed. - Write the script’s output to
mktemp -dand remove it in atrap ... EXIT. Never write into the tree and diff afterwards. - Compare against what git would commit. Run
git check-ignore --no-indexon the list of files the script wrote. - Make a failure print the hint first and the shortest evidence second. If the fix is one command, file names are enough.
- If a script and a Makefile target both run a tool, make the script call the target.
- When replacing a check, list what the old one did that you did not mean to drop. The old
git statuscheck worked from any directory, and the replacement lost that.
If a verify script of yours passed on your machine and failed in CI, reply on X.