Skip to content
Latchkey LogoLatchkey home

peter-evans/create-pull-request: one updated PR, and why its checks stall

peter-evans/create-pull-request commits whatever earlier steps changed to one fixed branch, create-pull-request/patch unless you name another, and creates, updates or closes a single pull request from it, so a scheduled job keeps one pull request current instead of opening one per run. Opened with the default GITHUB_TOKEN, that pull request's checks wait for someone to approve them and its pushes start no workflows, which is why most setups pass a GitHub App token.

peter-evans/create-pull-request is built for jobs that change files and want a person to review the result: dependency bumps, regenerated clients, translation syncs, formatted code. Earlier steps edit the workspace; this step notices the diff, commits it, pushes the branch and opens the pull request.

Two things surprise people the first time. The branch is rebuilt and force-pushed on every update, by design. And the pull request's checks do not run the way they do for a person's pull request.

Decision flow: diff against base and open pull request lead to created, updated, closed or none
Every run lands in one of four outcomes, reported in the pull-request-operation output. The token decides whether checks then run.

Create, update, close, or nothing

Each run compares the workspace with the base branch, which is the branch the job checked out unless you set base. Untracked files, modified files and local commits that were never pushed all count as changes. With a diff and no open pull request, it creates the branch and the pull request. With a diff and an open one, it rebuilds the branch on the current base and force-pushes it, so the pull request always shows the current diff. With no diff and an open pull request, it closes it, and deletes the branch if delete-branch is true. With no diff and nothing open, it exits quietly.

The pull-request-operation output says which happened: created, updated, closed or none. Branch on it rather than on the step's success, because all four are successes.

.github/workflows/regenerate.yml
name: Regenerate API client
on:
  schedule:
    - cron: '17 6 * * 1-5'
  workflow_dispatch:
permissions:
  contents: write
  pull-requests: write
jobs:
  regenerate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - run: make generate-client
      - id: cpr
        uses: peter-evans/create-pull-request@3ffccacd46053766916f087ce41d627c0b8cf2d1 # v8.1.3
        with:
          branch: bot/generated-client
          commit-message: 'chore: regenerate API client'
          title: 'chore: regenerate API client'
          labels: automated
          delete-branch: true
      - if: steps.cpr.outputs.pull-request-operation == 'created'
        env:
          PR_URL: ${{ steps.cpr.outputs.pull-request-url }}
        run: echo "Opened $PR_URL"

Inputs, and two repository settings

Every input is optional. Two settings outside the workflow also have to be right. The repository, or its organization, must allow GitHub Actions to create pull requests under Settings, Actions, General, Workflow permissions; without it the step fails with GitHub Actions is not permitted to create or approve pull requests. And a repository created after 2 February 2023 gives GITHUB_TOKEN read-only access unless the workflow asks for contents: write and pull-requests: write.

InputDefaultNotes
tokenGITHUB_TOKENCreates and updates the pull request
branch-tokenthe value of tokenCreates and updates the branch; rarely needed
pathGITHUB_WORKSPACERepository path relative to the workspace
add-pathsall new and modified filesGit pathspecs to commit; other changes are stashed and restored
commit-message[create-pull-request] automated changeText after a blank line becomes the commit description
committer, authorgithub-actions[bot], the triggering actorBoth ignored when sign-commits is true
signofffalseAdds a Signed-off-by line
branchcreate-pull-request/patchThe pull request branch; the action owns it
delete-branchfalseDelete branch when its pull request no longer has a diff
branch-suffixnonerandom, timestamp or short-commit-hash: a new branch per run
basethe checked-out branchRequired for events that check out a commit
push-to-forknoneowner/repo-fork to push the branch to
sign-commitsfalseSign as github-actions[bot] or as your App's bot
title, body, body-pathgeneric textbody-path wins over body
labels, assignees, reviewers, team-reviewers, milestonenoneteam-reviewers needs a PAT or App token
draftfalsetrue on create only, always-true on create and update
maintainer-can-modifytrueWhether maintainers can push to the branch

The branch belongs to the action

The action rebuilds the pull request branch from the base and force-pushes it on every update. Its documentation gives the reason: creating and updating should always produce the same result, and rebasing then force-pushing is the simplest way to guarantee it. The practical consequence is to never push your own commits to that branch, because the next run replaces them.

For the same reason, pointing branch at an existing branch that already holds your changes does not open a pull request for it; the action expects to create and manage the branch itself. For that case its documentation recommends gh pr create. branch-suffix creates a fresh branch per run instead, which the README marks as not recommended because it can pile up pull requests.

Events that check out a commit rather than a branch, such as pull_request (a merge commit) and release (a tag), need base set explicitly, for example base: ${{ github.head_ref }} or base: main.

Why checks on the pull request do not run

GitHub does not start workflows from events caused by GITHUB_TOKEN, so that a workflow cannot trigger itself. Pull requests now have an exception: when a workflow using GITHUB_TOKEN opens or updates one, its pull_request runs for opened, synchronize and reopened are created in an approval-required state, and someone with write access has to select Approve workflows to run. push workflows on the branch still do not run at all. The action's README still says the checks never run; either way a reviewer sees required checks that are not green.

The fix with the best trade-off is a GitHub App token from actions/create-github-app-token, passed as token: both push and pull_request workflows run, the pull request is opened by <app-slug>[bot], and the token is scoped to one installation and lives an hour. The other workarounds from the action's documentation are in the table.

.github/workflows/regenerate.yml
- id: app
  uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
  with:
    client-id: ${{ vars.APP_CLIENT_ID }}
    private-key: ${{ secrets.APP_PRIVATE_KEY }}
    permission-contents: write
    permission-pull-requests: write
- uses: peter-evans/create-pull-request@3ffccacd46053766916f087ce41d627c0b8cf2d1 # v8.1.3
  with:
    token: ${{ steps.app.outputs.token }}
    sign-commits: true
Workaroundpush workflowspull_request workflowsCost
GITHUB_TOKEN as it isDo not runWait for approvalA click on every update
GitHub App tokenRunRunA one-time App setup
PAT on a machine accountRunRunAn extra account; it cannot approve its own pull requests
SSH deploy key for the pushRunDo not runA key per repository
Machine account pushing to its fork (push-to-fork)Do not runRunA fork to maintain
draft: always-true with ready_for_reviewDo not runRun when a person marks it readyA manual click per update

Signed commits

Since v7 the action can sign its commits without a GPG key. Set sign-commits: true and the commits are signed as github-actions[bot] with GITHUB_TOKEN, or as your App's bot with an App token, and show as verified. A personal access token still creates the pull request but its commits are not signed, and committer and author are ignored whenever signing is on.

This path has a size limit: the GitHub API refuses blobs over 40 MiB, so a file that large in the pull request raises an error, and the documentation points to GPG signing for those repositories. The pull-request-commits-verified output reports whether GitHub considers the branch's commits verified, which is what a rule requiring signed commits checks.

Upgrade past v7.0.9 if you use checkout v6

One cross-action break matters more than any row here: with actions/checkout v6 or later, versions of this action older than v7.0.9 fail at the push with remote: Duplicate header: "Authorization". The maintainer's answer in issue #4272 was to upgrade; v8 is not affected.

MajorChange
v5Leaves the repository on the branch it started on; add-paths stashes and restores other changes; body-path input
v6Node 20; default author and committer gained the numeric ID prefix; git-token input
v7git-token renamed branch-token; sign-commits; draft: always-true; maintainer-can-modify; primary rate limits retried; pull-request-operation can be none
v8Node 24, needing runner v2.327.1 or later

Key takeaways

  • Four outcomes per run, created, updated, closed or none, all reported in pull-request-operation.
  • The branch is rebuilt and force-pushed on every update; never commit to it by hand.
  • With GITHUB_TOKEN, pull_request checks wait for approval and push workflows never run; an App token fixes both.
  • sign-commits: true gives verified commits with GITHUB_TOKEN or an App token, not with a PAT.

Frequently asked questions

Why don't pull requests created by create-pull-request trigger other workflows?
Because they were created with GITHUB_TOKEN, and GitHub does not start workflows from that token's events, except that pull_request runs are now created waiting for approval. Pass a GitHub App token or a machine account's PAT as token and the checks run as they do for a person.
Why are actions still not triggering with a custom App token?
In issue #2461 the token was fine: the workflow's on: pull_request: branches: filter named the pull request's own branch. That filter matches the base branch, the one the pull request targets. Check that too, and that the App is installed on the repository.
How do I fix Error: GitHub Actions is not permitted to create or approve pull requests?
Turn on the setting that lets GitHub Actions create pull requests, under Settings, Actions, General, Workflow permissions, at the repository or organization level. The permissions block in the workflow can be correct and the step still fails while that setting is off.
Can I use an existing branch as the pull request branch?
No. The action creates and manages the branch itself, and its common issues page says pointing branch at an existing branch with changes will not work. To open a pull request for a branch you already have, run gh pr create in a step.

References

Agents open pull requests all day. Latchkey Agent activity shows what each key ran. Start free → 30-day trial · No credit card