Skip to content

Instantly share code, notes, and snippets.

@joemiller
Last active February 22, 2026 16:06
Show Gist options
  • Select an option

  • Save joemiller/abc8f233e3a71e1b60669c250ff19d87 to your computer and use it in GitHub Desktop.

Select an option

Save joemiller/abc8f233e3a71e1b60669c250ff19d87 to your computer and use it in GitHub Desktop.
`git sign <PR#>` - resign all commits in a github PR with your gpg/ssh key
#!/usr/bin/env bash
set -eou pipefail
TMPDIR=""
GREEN=$'\033[32m'
RESET=$'\033[0m'
msg() { echo "${GREEN}==> $1${RESET}"; }
usage() {
cat <<EOF
Usage: git sign-pr <pr-number>
Re-sign all commits in a pull request with your GPG/SSH key.
Clones the repo into a temporary directory, rebases the PR's commits to
re-sign them, and force-pushes (with lease) back to the remote.
Must be run from within a git repository that has an 'origin' remote.
Requires: gh (GitHub CLI)
EOF
}
main() {
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
if ! command -v gh &>/dev/null; then
echo "error: 'gh' (GitHub CLI) is required but not found on PATH" >&2
echo >&2
usage >&2
exit 1
fi
if [[ $# -eq 0 ]]; then
usage >&2
exit 1
fi
local pr="$1"
local local_repo
local_repo="$(git rev-parse --show-toplevel)"
local remote_url
remote_url="$(git remote get-url origin)"
local gh_repo
gh_repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner)"
local state head_branch base
read -r state head_branch base < <(
gh pr view "$pr" --repo "$gh_repo" \
--json state,headRefName,baseRefName \
--jq '[.state, .headRefName, .baseRefName] | @tsv'
)
if [[ "$state" != "OPEN" ]]; then
echo "error: PR #${pr} is not open (state: ${state})" >&2
exit 1
fi
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
msg "Cloning from '${local_repo}' -> '${TMPDIR}'..."
git clone --quiet --reference-if-able "$local_repo" "$remote_url" "$TMPDIR"
cd "$TMPDIR"
msg "Checking out PR #${pr} (${head_branch})..."
git checkout --quiet -B "$head_branch" "origin/$head_branch"
git branch --quiet --set-upstream-to="origin/$head_branch"
local merge_base count
merge_base="$(git merge-base HEAD "origin/${base}")"
count="$(git rev-list --count "${merge_base}..HEAD")"
msg "Re-signing ${count} commit(s)..."
git rebase --quiet --exec 'git commit --amend --no-edit' "$merge_base"
msg "Pushing..."
git push --quiet --force-with-lease
msg "Done. PR #${pr} commits re-signed."
}
main "$@"

git sign-pr

A git subcommand that re-signs all commits in a pull request with your GPG/SSH key.

Why?

Cloud-based AI coding agents (e.g. Cursor's cloud agents) can open PRs on your behalf, but their commits are unsigned. If your repository requires signed commits, these PRs are blocked.

git sign-pr rebases the PR's commits to re-sign them with your local signing key and force-pushes (with lease) back to the branch. As of Q1 2026, this is the simplest workaround.

How it works

  1. Validates the PR exists and is open (before doing any heavy work)
  2. Clones the repo into a temp directory using your local clone as a reference (fast, no full re-download)
  3. Rebases the PR's commits, re-signing each one
  4. Force-pushes with lease

Requirements

  • gh (GitHub CLI), authenticated
  • Git commit signing configured (user.signingkey, commit.gpgsign, etc.)

Install

Download git-sign-pr and place it anywhere on your $PATH:

chmod +x git-sign-pr
# move it to wherever you keep your binaries, e.g.:
mv git-sign-pr ~/.local/bin/

Git automatically discovers subcommands named git-<name> on your $PATH.

Usage

From within a local clone of the repository:

git sign-pr <pr-number>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment