Skip to content

Instantly share code, notes, and snippets.

@swinton
Last active May 4, 2025 12:04
Automatically sign your commits from GitHub Actions, using the REST API

Verified commits made easy with GitHub Actions

image

So you want to commit changes generated by a GitHub Actions workflow back to your repo, and have that commit signed automatically?

Here's one way this is possible, using the REST API, the auto-generated GITHUB_TOKEN, and the GitHub CLI, gh, which is pre-installed on GitHub's hosted Actions runners.

You don't have to configure the git client, just add a step like the one below... Be sure to edit FILE_TO_COMMIT and DESTINATION_BRANCH to suit your needs.

    # Use the REST API to commit changes, so we get automatic commit signing
    - name: Commit changes
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        FILE_TO_COMMIT: data.csv
        DESTINATION_BRANCH: data
      run: |
        export TODAY=$( date -u '+%Y-%m-%d' )
        export MESSAGE="chore: regenerate $FILE_TO_COMMIT for $TODAY"
        export SHA=$( git rev-parse $DESTINATION_BRANCH:$FILE_TO_COMMIT )
        export CONTENT=$( base64 -i $FILE_TO_COMMIT )
        gh api --method PUT /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
          --field message="$MESSAGE" \
          --field content="$CONTENT" \
          --field encoding="base64" \
          --field branch="$DESTINATION_BRANCH" \
          --field sha="$SHA"

Caveats

Because of the underlying REST API, only 1 file can be committed at a time.

Notes

This is made possible because GitHub automatically signs commits from bots over the REST API. Since the GITHUB_TOKEN is a bot token, this also applies to commits from GitHub Actions.

See this blog post from 2019 for more details: https://github.blog/2019-08-15-commit-signing-support-for-bots-and-other-github-apps/

@rotty3000
Copy link

rotty3000 commented Jan 20, 2025

Referencing a dev.to post about making commits using gh's graphql api, Here's a solution that can handle an arbitrary number of files:

# Collect all changed file names
CHANGED=($(git diff --name-only | xargs))

for value in "${CHANGED[@]}"; do
  FILES="${FILES} -F files[][path]=$value -F files[][contents]=$(base64 -w0 $value)"
done

gh api graphql \
	-F githubRepository=${GIT_REPOSITORY} \
	-F branchName=${PUBLISH_BRANCH} \
	-F expectedHeadOid=$(git rev-parse HEAD) \
	-F commitMessage="commit by github-actions[bot]" \
	-F "query=@.github/api/createCommitOnBranch.gql" \
	${FILES}

You do need to create the .github/api/createCommitOnBranch.gql file:

mutation (
    $githubRepository: String!,
    $branchName: String!,
    $expectedHeadOid: GitObjectID!
    $commitMessage: String!
    $files: [FileAddition!]!
) {
  createCommitOnBranch(
    input: {
    branch:
    {
        repositoryNameWithOwner: $githubRepository,
        branchName: $branchName
    },
    message: {headline: $commitMessage},
    fileChanges: {
        additions: $files
    }
    expectedHeadOid: $expectedHeadOid
    }
  ){
    commit {
    url
    }
  }
}

@rishabhc32
Copy link

rishabhc32 commented Apr 16, 2025

Was getting this error:

/usr/bin/gh: Argument list too long

Added this to fix:

 base64 -w 0 "$FILE_TO_COMMIT" > encoded_content.txt
 
 gh api --method PUT /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
  --field "message=${MESSAGE}" \
  --field "content=@encoded_content.txt" \
  --field "encoding=base64" \
  --field "branch=${DESTINATION_BRANCH}" \
  --field "sha=${SHA}"
)

@qoomon
Copy link

qoomon commented Apr 16, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment