backport: sign commits by publishing via the GitHub GraphQL API - #293
Open
pracucci wants to merge 1 commit into
Open
backport: sign commits by publishing via the GitHub GraphQL API#293pracucci wants to merge 1 commit into
pracucci wants to merge 1 commit into
Conversation
Replace the unsigned 'git push --set-upstream origin <head>' with a createCommitOnBranch GraphQL mutation. The cherry-pick is still done locally (preserving the conflict-resolution logic and the betterer fallback), but the resulting commit is published to the new branch via the API so it is signed by GitHub's web-flow key (shown as Verified). Trade-off: the API uses the authenticated identity (typically the GitHub App running the action) as the commit author and committer. The original commit's author is preserved via a Co-authored-by trailer appended to the commit message, so attribution survives. Limitations: - File mode changes (e.g. executable bit) are not preserved because createCommitOnBranch's FileAddition does not accept a mode. Most backports don't touch modes, so this is rarely an issue in practice. - Renames are flattened into delete-plus-add (we pass --no-renames to git diff).
kminehart
approved these changes
May 28, 2026
Author
|
I will keep this PR open in case anyone using this action needs signed commits (Mimir repository migrated to https://github.com/grafana/grafana-github-actions-go in the meanwhile). The reason why I'm not merging it is because since Mimir migrated to I leave to anyone relying on this action to merge (or ask to merge) and then take ownership to test it end-to-end. |
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.
Summary
Replace the unsigned
git push --set-upstream origin <head>in thebackportaction with acreateCommitOnBranchGraphQL mutation. The cherry-pick still runs locally (preserving the conflict-resolution logic and the betterer fallback), but the resulting commit is published to the new branch via the GitHub API, so it is signed by GitHub's web-flow key and shows as Verified in the UI.Why
The backport action is consumed by Grafana repos whose CI flows run from GitHub Apps. Apps can't have GPG/SSH signing keys registered against them, so
git pushfrom the runner produces an unsigned commit. This is friction wherever signed commits are part of the audit/provenance story for a repository.The fix is server-side: GitHub's
createCommitOnBranchmutation signs commits with the web-flow key automatically — the same mechanism the "Merge pull request" button uses.How
In
backport/backport.ts:git switch base, capture the base SHA viagit rev-parse HEAD.FileChangespayload by diffing base → HEAD withgit diff --no-renames --name-status -z:D→deletions[{ path }].A/M/T→additions[{ path, contents (base64 of file bytes) }].github.git.createRef.createCommitOnBranchvia@octokit/graphql(already a transitive dependency) with the file changes and the cherry-pick's commit message. The original author ofcommitToBackportis preserved as aCo-authored-by:trailer so attribution survives.github.pulls.create()etc.The local
git push --set-upstreamis removed.Trade-offs
author:field. Attribution is recovered via theCo-authored-by:trailer added to the commit message — GitHub renders co-authors with avatars in the commit view.Co-authored-by:trailer, because the signature attests the API call but not the co-author claim. The API still returnsverification.verified: true/reason: valid, which is what branch-protection "Require signed commits" rules check — confirmed by enabling the rule on a sandboxrelease-1.0branch and successfully merging a backport PR whose commit had the "Partially verified" badge.createCommitOnBranch'sFileAdditioninput doesn't accept a mode. Most backports don't touch modes; flagging here for completeness.--no-renamestogit diff. The diff content reaching the destination branch is identical, only the visual rendering on GitHub differs.Tests
backport/backport.test.tscoverage for the newbuildFileChangeshelper, exercising additions, modifications, and deletions against a temp git repo.Compiled JS
backport/backport.jsis regenerated and committed alongside the TS, matching the repo's convention.Test plan
Tested end-to-end in https://github.com/pracucci/backport-test (sandbox, no secrets):
github.pulls.create()fails withnot permitted to create or approve pull requests):gh api repos/pracucci/backport-test/actions/permissions/workflow -X PUT -F can_approve_pull_request_reviews=true -F default_workflow_permissions=write..github/workflows/backport.ymltriggered onpull_request: [closed, labeled]; checks out this PR's branch at refce15900ea5af59b4af848d268b33438c0693f10binto./actions, installs deps viacorepack enable && yarn install --immutable(the project enforcesengine-strict=trueagainst npm), then runs./actions/backportwithtoken: ${{ secrets.GITHUB_TOKEN }}.release-1.0branch created offmainas the backport target.feature-7 → main) adding a new filedocs/feature-7.md— new files avoid the cherry-pick conflicts you get from modifying files that have diverged.backport release-1.0+type/bug) usinggh api .../issues/7/labels --method POST --input -. The action gates on both: abackport <branch>label AND a category label (type/bug/type/docs/type/ci/product-approved).gh pr edit --add-labelcurrently silently no-ops due to a Projects-classic GraphQL deprecation error in the CLI.gh pr merge 7 --squash, firingclosedwithmerged: true→ action ran.backport-7-to-release-1.0:verification.verified: true,reason: valid; committerGitHub, authorgithub-actions[bot]; messageAdd Feature 7 doc (#7)\n\n(cherry picked from commit 9f00f5a6e3…)\n\nCo-authored-by: Marco Pracucci <marco@grafana.com>— cherry-pick attribution + Co-authored-by trailer both intact; onlydocs/feature-7.mdmodified.[release-1.0] Add Feature 7 doc) with the original PR'stype/buglabel plusbackportcarried over.release-1.0and merged PR Github action for signing plugins #8 — succeeded, so a UI-"Partially verified" backport commit is accepted by the rule (it'sverification.verified: trueat the API level).Findings during testing
EBADENGINE—.npmrchasengine-strict=trueandpackage.jsondeclaresengines.npm: please-use-yarn. Consumers pinning to this branch (or anything newer than the npm-blocker) must switch their caller workflow fromnpm installtocorepack enable && yarn install --immutable.README.mdwhich had diverged onmain. Use new files for backport tests; same caveat applies to any cherry-pick on a divergent branch.github.pulls.create()withnot permitted to create or approve pull requests(a repo-level setting). The branch + Verified commit were created successfully before the failure (pracucci/backport-test@90785ef76d), so the signing-related code paths in this PR were already exercised. Enabling the permission setting let the next run complete end-to-end.