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

> peter-evans/create-pull-request keeps one PR current from a workflow. See when it creates or updates, why GITHUB_TOKEN stalls checks, and the fixes.

Source: https://latchkey.dev/learn/actions/peter-evans-create-pull-request  
Updated: 2026-11-12

`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.

## 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](/learn/github-actions/gha-create-pull-request-token-403). 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`.

| Input | Default | Notes |
| --- | --- | --- |
| `token` | `GITHUB_TOKEN` | Creates and updates the pull request |
| `branch-token` | the value of `token` | Creates and updates the branch; rarely needed |
| `path` | `GITHUB_WORKSPACE` | Repository path relative to the workspace |
| `add-paths` | all new and modified files | Git pathspecs to commit; other changes are stashed and restored |
| `commit-message` | `[create-pull-request] automated change` | Text after a blank line becomes the commit description |
| `committer`, `author` | `github-actions[bot]`, the triggering actor | Both ignored when `sign-commits` is true |
| `signoff` | `false` | Adds a `Signed-off-by` line |
| `branch` | `create-pull-request/patch` | The pull request branch; the action owns it |
| `delete-branch` | `false` | Delete `branch` when its pull request no longer has a diff |
| `branch-suffix` | none | `random`, `timestamp` or `short-commit-hash`: a new branch per run |
| `base` | the checked-out branch | Required for events that check out a commit |
| `push-to-fork` | none | `owner/repo-fork` to push the branch to |
| `sign-commits` | `false` | Sign as `github-actions[bot]` or as your App's bot |
| `title`, `body`, `body-path` | generic text | `body-path` wins over `body` |
| `labels`, `assignees`, `reviewers`, `team-reviewers`, `milestone` | none | `team-reviewers` needs a PAT or App token |
| `draft` | `false` | `true` on create only, `always-true` on create and update |
| `maintainer-can-modify` | `true` | Whether maintainers can push to the branch |

> Outputs: `pull-request-number`, `pull-request-url`, `pull-request-operation`, `pull-request-head-sha`, `pull-request-branch` and `pull-request-commits-verified`. From `action.yml` and the README at v8.1.3, read 2026-11-12.

## 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](/learn/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
```

| Workaround | push workflows | pull_request workflows | Cost |
| --- | --- | --- | --- |
| `GITHUB_TOKEN` as it is | Do not run | Wait for approval | A click on every update |
| GitHub App token | Run | Run | A one-time App setup |
| PAT on a machine account | Run | Run | An extra account; it cannot approve its own pull requests |
| SSH deploy key for the push | Run | Do not run | A key per repository |
| Machine account pushing to its fork (`push-to-fork`) | Do not run | Run | A fork to maintain |
| `draft: always-true` with `ready_for_review` | Do not run | Run when a person marks it ready | A manual click per update |

> Trigger rules from docs.github.com, Triggering a workflow from a workflow, read 2026-11-12. Workarounds from the action's concepts and guidelines document at v8.1.3.

## 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.

| Major | Change |
| --- | --- |
| v5 | Leaves the repository on the branch it started on; `add-paths` stashes and restores other changes; `body-path` input |
| v6 | Node 20; default `author` and `committer` gained the numeric ID prefix; `git-token` input |
| v7 | `git-token` renamed `branch-token`; `sign-commits`; `draft: always-true`; `maintainer-can-modify`; primary rate limits retried; `pull-request-operation` can be `none` |
| v8 | Node 24, needing runner v2.327.1 or later |

> From `docs/updating.md` at v8.1.3 and issue #4272 on the action's repository, read 2026-11-12.

## FAQ

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

- [peter-evans/create-pull-request README (read 2026-11-12)](https://github.com/peter-evans/create-pull-request)
- [create-pull-request action.yml at v8.1.3](https://github.com/peter-evans/create-pull-request/blob/v8.1.3/action.yml)
- [Concepts, guidelines and advanced usage](https://github.com/peter-evans/create-pull-request/blob/v8.1.3/docs/concepts-guidelines.md)
- [Triggering a workflow from a workflow, docs.github.com](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow)
- [Issue #4272: Duplicate header Authorization with actions/checkout v6+](https://github.com/peter-evans/create-pull-request/issues/4272)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
