Skip to content

Commits via git are unverified; switch to GraphQL for commits #18565

Description

@strawgate

When using the GITHUB_TOKEN via git the resulting commits show up as unverified.

If the same commits are made with the GITHUB_TOKEN through the graphql api (as discussed here https://gist.github.com/swinton/03e84635b45c78353b1f71e41007fc7c?permalink_comment_id=5401086#gistcomment-5401086 ):

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
    }
  }
}

The resulting commits appear as signed/verified.

Activity

  1. locked and limited conversation to collaborators on Feb 26, 2026
  2. unlocked this conversation on Feb 26, 2026
  3. pelikhan commented on Feb 27, 2026

    @pelikhan
    Collaborator

    /scout collect info

  4. pelikhan commented on Feb 27, 2026

    @pelikhan
    Collaborator

    /scout collect info

  5. github-actions commented on Feb 27, 2026

    @github-actions
    Contributor

    🔍 Scout Research Report

    Triggered by @pelikhan

    Executive Summary

    The issue is well-understood and has a clean solution. Commits made via git commit + git push using GITHUB_TOKEN are unverified because GitHub does not automatically GPG-sign commits made through the standard Git protocol. The createCommitOnBranch GraphQL mutation (released by GitHub in Sep 2021) automatically signs commits using GitHub's own GPG key, making them appear as Verified in the UI — no GPG key management required.

    This repo currently uses git commit + git push in at least two places: format-and-commit.yml and scripts/changeset.js, plus many .lock.yml workflows that instruct AI agents to use git commit.

    Research Findings

    Why git commits via GITHUB_TOKEN are unverified

    GitHub Actions' GITHUB_TOKEN is a bot token. When you use it to authenticate git push over HTTPS, the commits are created locally without a cryptographic signature. GitHub has no way to verify those commits because there is no GPG/SSH key attached.

    From GitHub Docs on commit signature verification:

    GitHub will automatically use GPG to sign commits you make using the web interface. Commits signed by GitHub will have a verified status.

    The key: only commits made through GitHub's API (web, GraphQL, REST blob+tree) are auto-signed.

    The createCommitOnBranch GraphQL Mutation

    Released September 2021 ([GitHub Changelog]((github.blog/redacted)

    Commits authored using the new API are automatically GPG signed and are marked as verified in the GitHub UI. GitHub Apps can use the mutation to author commits directly or on behalf of users.

    Key advantages over the older REST blob/tree approach:

    • Single API call for multiple files (no separate blob + tree + commit + ref steps)
    • Auto-signed/verified by default
    • Works with GITHUB_TOKEN, GitHub App tokens, and PATs

    Implementation Approaches

    Option 1: gh api graphql in shell (as described in the issue)

    Requires a .github/api/createCommitOnBranch.gql file and collecting changed files via git diff --name-only. Files are base64-encoded and passed as form fields. Works well for small-to-medium file sets.

    Known caveat: shell argument list length limit (/usr/bin/gh: Argument list too long) when many files change — workaround is to write the query body to a temp file.

    Option 2: actions/github-script with GraphQL

    Using @octokit/graphql in a workflow step handles both additions and deletions cleanly in JavaScript. No shell escaping issues, easier to handle dynamic file lists. Example from [StackOverflow discussion]((stackoverflow.com/redacted)

    Option 3: Third-party reusable Actions

    Action Approach Notes
    IAreKyleW00t/verified-bot-commit REST API (blob+tree+commit+ref) Linux/macOS/Windows; uses GITHUB_TOKEN or GitHub App token
    step-security/github-api-commit-action GraphQL createCommitOnBranch Fetches latest OID just before committing; works on checked-out branch
    Asana/push-signed-commits GraphQL createCommitOnBranch Python-based composite action; local commits → re-signs via API; uses git reset --hard to sync local

    Important Caveats

    1. Commit hash changes: When using the GraphQL API, GitHub signs and re-creates the commit server-side. The resulting SHA will differ from any locally created commit. Workflows must update their local branch ref after committing via API (e.g., git fetch && git reset --hard origin/branch).

    2. File size limit: API blob uploads are limited to ~40 MiB per file.

    3. "Web Commit Signing" org setting: Some reports indicate that for GitHub App tokens (not GITHUB_TOKEN), the org-level "Web Commit Signing" setting may need to be enabled. For standard GITHUB_TOKEN in GitHub Actions, this is not an issue.

    4. Deletions: createCommitOnBranch supports both additions and deletions in fileChanges. The gh api graphql shell approach requires extra work to handle deleted files (not just modified/added ones).

    Current State in This Repo

    The following places currently use unverified git commit:

    • .github/workflows/format-and-commit.yml — uses git add -A && git commit + git push
    • scripts/changeset.js — uses git commit -m "Release ..." + git push
    • Multiple .lock.yml agent workflows — instruct the AI agent via # --allow-tool shell(git commit:*) / # - Bash(git commit:*) comments

    Recommendations

    1. Replace git commit + git push in format-and-commit.yml with a gh api graphql step using the createCommitOnBranch mutation. This is the highest-impact, most straightforward change.

    2. Update scripts/changeset.js to use the GraphQL API for the release commit, or use a reusable action like step-security/github-api-commit-action.

    3. For AI agent lock workflows (.lock.yml files), consider adding a post-commit step that re-creates the commit via GraphQL, or instruct the agent to use the GraphQL API directly instead of git commit. The Asana/push-signed-commits composite action pattern (local commit → API re-sign → local reset) may be a good fit here.

    4. Consider a shared reusable workflow for the GraphQL commit step to avoid duplicating the mutation across many workflows.

    Key Sources

    References: §22472950127

    🔭 Intelligence gathered by Scout

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions