From b0db3e652d11c97ae259e0fed99b8a1cade7dba6 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:29:55 +0100 Subject: [PATCH] docs(signing): squash signs the result, not the PR branch required_signatures checks every commit on the PR branch before a merge, so a squash merge is not a way for bots to get signed commits: one unsigned head commit blocks the PR (measured on pons-asinorum #46). Drop the squash route from the apps row, and add a section with the measurement and the cherry-pick -S recovery. Raised by CodeRabbit on rpa-elysium#142. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WRvDivYwLSeVCJUrfjic3f --- docs/SIGNING-POLICY.adoc | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/SIGNING-POLICY.adoc b/docs/SIGNING-POLICY.adoc index 210327fd5..d13a69cd4 100644 --- a/docs/SIGNING-POLICY.adoc +++ b/docs/SIGNING-POLICY.adoc @@ -17,9 +17,10 @@ it supersedes the sequencing in D90. gitbot-fleet, AI tools running unattended) | GitHub's web-flow **GPG** key | Write through the API so GitHub signs the commit itself: GraphQL - `createCommitOnBranch`, the estate - link:../.github/actions/signed-push/README.md[`signed-push`] action, or a - squash merge of a PR. Never `git push` a locally made commit. + `createCommitOnBranch` or the estate + link:../.github/actions/signed-push/README.md[`signed-push`] action. Never + `git push` a locally made commit. A squash merge is *not* a way round this: + see <>. | **Humans and interactive agents** (the owner, collaborators, Claude Code and other agents running on a person's machine) @@ -44,7 +45,8 @@ These were each measured on default branches between 2026-07 and 2026-09. Do not * **Rebase-merge.** It replays the PR's commits *unsigned* under the merger's identity (D89). Rebase-merge is off wherever signatures are required. Use squash - instead: GitHub signs the squash commit. + instead: GitHub signs the squash commit. Squash does not excuse the commits on + the PR branch, though (<>). * **The contents API (`PUT /repos/.../contents`) with a user OAuth token or PAT.** The commit is unsigned. Use `createCommitOnBranch` instead. * **`git push` from a workflow** using `GITHUB_TOKEN` or a PAT. The commit is @@ -54,6 +56,22 @@ These were each measured on default branches between 2026-07 and 2026-09. Do not * **A second machine or container without signing configured.** Configure it before its first push. +[[pr-branch-commits]] +=== Squash signs the result, not the PR branch + +`required_signatures` checks *every commit on the PR branch* before it allows a +merge, not just the commit that will land. GitHub signs the squash commit, but +one unsigned commit on the head still blocks the PR. This was measured on +pons-asinorum #46 (2026-09-30): with `required_signatures` as the only active +rule, a single unsigned bot commit left the PR `BLOCKED`, and +`gh pr merge --squash` was refused with "base branch policy prohibits". + +So every commit pushed to a PR branch must itself be signed. When one is not, +re-create the branch from the default branch with `git cherry-pick -S` (the +original author is kept; the re-signer adds a trailer). Check that +`git diff HEAD` is empty, open a new PR, and close the old one. Do not +force-push, and do not merge with `--admin`. + == Enforcement The ruleset canon is