fix(ci): stop the validation resolver losing a race with its own PR run - #82
Merged
Merged
Conversation
The receipt was never being reused, and it was not tree drift. Measured on sneat-co/sneat-go (merge run 34165753334): the receipt artifact was created at 22:11:13, the pull-request run concluded at 22:11:18, and the resolver asked at 22:11:21 -- three seconds later -- and was told there were no successful pull-request runs. Verified afterwards through the same API calls the resolver makes: the run matches every filter and the receipt artifact is present and unexpired. GitHub's run index simply had not caught up. This is structural, not bad luck. The merge run starts the moment auto-merge fires, which is when the required checks pass -- and the receipt is published by a job that only runs after them, so it is routinely the last thing to exist. The optimization lost a race with the very run it depends on, on every merge. So wait, but only for evidence that is visibly still arriving: a pull-request run for that head which has not concluded, or a successful run whose receipt has not appeared. Bounded at 90 seconds inside the job's existing 3-minute timeout. Every settled refusal -- a direct push, a failed run, a tree mismatch, an ambiguous pair -- is decided on the first look and waits for nothing, so a genuine miss never costs the merge run more than the resolver's own runtime. The runs query drops its server-side `status=success` filter and concludes in code instead: a run that is still finishing is precisely the signal that waiting is worthwhile, and that filter hid it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETcdTvH64PAweR3aZ3smpL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #80/#81. I said there that the receipt never matched because of tree drift. That was wrong, and the evidence says so plainly.
What actually happens
Measured on sneat-co/sneat-go, merge run 34165753334:
Its verdict:
expected one successful pull request validation receipt, found 0.I then replayed the resolver's own API calls by hand. The run matches every filter it applies:
{"id":34165180456,"event":"pull_request","conclusion":"success", "head_branch":"sneatai-fastpath","head_sha":"6efb0b98…","head_repo":"sneat-co/sneat-go"}and the receipt is there:
{"name":"ci-validation-receipt","expired":false,"size_in_bytes":840}, including through the exact?name=ci-validation-receiptquery. GitHub's run index had simply not caught up three seconds after the run concluded.Why it is structural, not bad luck
The merge run starts the moment auto-merge fires — which is when the required checks pass. The receipt is published by a job that only runs after those checks, so it is routinely the last thing in the run to exist. The optimization was losing a race with the very run it depends on, on every single merge. That is why
reuse_exact_tree_validationhas been enabled in sneat-go since it shipped and has never once fired.The fix
Wait — but only for evidence that is visibly still arriving:
Bounded at 90 s, inside the resolver job's existing 3-minute timeout.
Every settled refusal is decided on the first look and waits for nothing: a direct push (no pull request now and never), a failed run, a tree mismatch, an ambiguous pair. So a genuine miss never costs the merge run more than the resolver's own runtime — which matters, because lint and tests wait on this job.
The runs query drops its server-side
status=successfilter and concludes in code instead: a run that is still finishing is exactly the signal that waiting is worthwhile, and that filter hid it.Tests
go test ./...green. Two new ones:maintoday.pollIntervalis set to an hour, so any wait at all hangs the test rather than passing it. Covers a direct push, a failed run, and a tree mismatch.Note this also unblocks
validate_on_mainfrom #81, which was losing the same race — its delegated path needs the same run listing.🤖 Generated with Claude Code
https://claude.ai/code/session_01ETcdTvH64PAweR3aZ3smpL