Repository navigation
Description
Problem
check-signed-commits escalates a pull request as having unverified commits when it could not check them at all, and names no commit. The lane commits in these runs are signed; nothing was caught unsigned and no fix was discarded.
Seen twice on 2026-10-21:
- pr-unblock address-feedback on #8870, run 39615724332: the lane moved
work-resolve-issue/issue-8734-39613836173-1from2a21d0b1to45e6f6fdat 14:20:58.45e6f6fdis GitHub-verified (reason: valid), its parent is the recordedsince-sha, and it is still the PR's head. The check ran at 14:21:19 and loggedno comparison from the recorded head; checked the PR's commits, then##[warning]check-signed-commits: unverified commitswith an empty list.needs-humanwas applied at 14:24:04. - pr-refine fix-docs on #8844, run 39619999852:
ACT_OUTCOME: skipped, so the model never ran and the lane made no commit. The check logged the same two lines, with an empty list again.
Cause
The PR's head as GET /pulls reports it never matched the branch ref, so the check could not confirm the head. It reported that as "unverified commits". It was not a failed read, not a commit-list query error, and not a pre-push check: the commit list was read, and the check runs after the push.
In .github/actions/check-signed-commits/check-signed-commits.mjs:
readPull(:42-54) reads the pull and the branch'sgit/refup toHEAD_READS = 5times, 2 s apart (:17-18), and returnsheadConfirmed: falsewhen they never agree.- feat(corrections): add corrections:codify #8870:
GET /pullsstill returned2a21d0b1at 14:21:45 (the report job's own pr-head step,pr-run-activity-write.yml~:2310-2321), about 47 s after the ref moved. - fix(guardrails): anchor the merge guard's backtick refusal to a gh command word #8844: the branch ref is
edaa1554(a base merge committed at 14:44:36), but the PR head is1dd5e843, then and now.edaa1554has no check runs, so GitHub never synced the PR to it. Why is not known.
- feat(corrections): add corrections:codify #8870:
- With
headConfirmedfalse the comparison is skipped (:151-153), sosinceIsAncestoris false and :210 logs "no comparison from the recorded head", although the recorded head was an ancestor. checkableis false (:156).unverifiedis computed over the PR's commits, which are all verified, so it is[].allVerifiedis false (:165).- :215 logs
unverified commits ${[].join(" ")}, and the escalation comment (:106) opens with "these commits are not GitHub-verified" even when no commit is unverified. Thehead did not match its branchline (:112) comes second.
Callers: pr-run-activity-write.yml:2290 and pr-run-activity-read.yml:1494.
Cost
A false needs-human hold: no lane continues on the PR until a human looks, and the log and comment send that human after a nonexistent unsigned commit.
Suggested fix
- Say "could not verify" (small). When
!checkable, logcheck-signed-commits: could not verify the lane's commits (PR head <pull.head.sha> never matched branch ref <refSha> after 5 reads)instead ofunverified commits. Lead the comment with the same reason, and list unverified SHAs only when there are some. Keepall-verified=false, so the run still fails closed. Add a test: head unconfirmed, every commit verified, the log says "could not verify" and never "unverified commits". - Wait longer (optional). The observed
/pullslag was at least 47 s, against a 5 × 2 s window. A backed-off wait of about 60 s would have cleared feat(corrections): add corrections:codify #8870. It would not clear fix(guardrails): anchor the merge guard's backtick refusal to a gh command word #8844. - Check against the ref (design, not small). Compare
since...refShawhen the head is unconfirmed. Hazard: the later filter against the PR commit list (:95-97) would use a stale list that lacks the lane's new commit (45e6f6fdhere) and pass it unchecked. This touches fix(ci): check-signed-commits trusts the PR's current base to drop merged-in commits #8240.
Related
- The labels 403 that followed both escalations is fix(ci): signed-commit escalation gets 403 because the report job holds no issues: write #8872, fixed by fix(ci): let the signed-commit escalation write its label and comment #8874.
- fix(ci): signed-commit check fails pr-refine on PRs over 250 commits even when the lane made no commits #7256 (PRs over 250 commits) and fix(ci): check-signed-commits trusts the PR's current base to drop merged-in commits #8240 (base-drop trust) do not cover this case.
Activity
github-actions commentedon Oct 21, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; a maintainer confirms or changes it.
Thanks for the detailed report. I understood it this way: when check-signed-commits can't confirm the PR head against the branch ref, it logs and comments "unverified commits" with an empty list, even though every commit is verified. That leads to a false needs-human hold. Your suggested fix has three parts. Part 1 is to report "could not verify" and keep failing closed. Part 2 is a longer wait. Part 3 is to compare against the ref, which is a design question tied to #8240. Nothing seems to be missing for part 1. Parts 2 and 3 would need a maintainer's decision. I found no likely duplicates among the open issues.
Automated triage. A maintainer reviews it.