Skip to content
Latchkey LogoLatchkey home

action-semantic-pull-request: PR title checks, and the trigger to run them on

amannn/action-semantic-pull-request fails a check when a pull request title does not follow Conventional Commits, such as feat(ui): Add Button component, so that squash merges produce commit messages a release tool can read. It reads the title through the API and never checks out code, which is why its README recommends pull_request_target; in public repositories GitHub starts blocking that trigger by default on December 2, 2026.

action-semantic-pull-request exists for one workflow: squash and merge, with the repository set to use the pull request title as the commit message, feeding a release tool such as semantic-release. Every title becomes a commit on the default branch, so a bad title is a bad changelog line.

Setting it up takes one step. Choosing the trigger takes more thought, because the README's recommended event is the one GitHub's security guidance now gates, and the right answer depends on whether you accept pull requests from forks.

Decision tree for choosing pull_request or pull_request_target for the PR title check
The action only reads the title, so the read-only token of a fork run is enough unless wip needs to write a status.

The workflow

The action reads GITHUB_TOKEN from the environment, not from an input, and needs only pull-requests: read. It fetches the pull request through the REST API instead of trusting the event payload, because the payload can be stale: a title fixed after the event still validates correctly when the run is re-run.

List edited in the event types, or fixing the title will not re-run the check. List synchronize if the check is required, because a required check has to report on the latest commit.

.github/workflows/pr-title.yml
name: Lint PR title
on:
  pull_request:
    types: [opened, edited, reopened, synchronize]
permissions:
  pull-requests: read
jobs:
  title:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@2252a14e810aa9b85c2d560bd7070c802206ecf6 # v6.1.2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          types: |
            feat
            fix
            docs
            chore
          scopes: |
            api
            ui

pull_request or pull_request_target

The difference is which copy of the workflow runs and what it can do. On pull_request, GitHub runs the workflow from the pull request's merge commit and, for a fork, gives it a read-only GITHUB_TOKEN and no secrets. On pull_request_target, it runs the workflow from the base repository's default branch with a token that can write and with secrets available.

For this action, read-only is enough: it only calls pulls.get and, with validateSingleCommit, pulls.listCommits. Issue #219 on the action's repository reports a fork's run on pull_request finishing successfully, and the maintainer agreed the README's warning about forks may be outdated. The exception is wip: true, which writes a commit status and needs write access a fork run does not get.

pull_request_target is safe for this action as published, because nothing from the fork executes: no checkout, no build, one API read. It stops being safe the moment someone adds a checkout of the pull request head and runs anything from it. In public repositories GitHub has also added a default event policy that blocks pull_request_target. It does not apply to private or internal repositories, runs in evaluate mode now, and GitHub enforces it on December 2, 2026 for repositories still on that default; after that, a workflow copied from the README stops running unless an Actions event policy allows the event.

So use pull_request unless you need wip on fork pull requests. If you keep pull_request_target, keep the job free of checkouts and grant it pull-requests: read and nothing else.

pull_requestpull_request_target
Workflow file usedFrom the pull requestFrom the default branch
A fork's run getsRead-only token, no secretsWrite-capable token and secrets
A config change in the pull request appliesImmediatelyOnly after it merges
wip: true on fork pull requestsFails without write accessWorks
Public repositories from December 2, 2026RunsBlocked unless an event policy allows it

Types, scopes and the failure messages

Every list input is newline separated; v5.0.0 switched from commas so that an entry may contain spaces. A title may name several scopes, feat(api,ui): ..., and each one is checked against scopes.

Failures are explicit. A missing type prints No release type found in pull request title "<title>". followed by the available types, an unknown type prints Unknown release type "<type>" found in pull request title "<title>"., and a required but absent scope prints No scope found in pull request title "<title>". The error_message output carries the same text, so a later step can post it as a comment.

InputWhat it does
typesAllowed types, each a regex wrapped in ^ $ (regex since v6.1.0). Default: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
scopesAllowed scopes as regexes; unset allows any scope
requireScopeFail when the title has no scope
disallowScopesScopes that fail the check
subjectPatternRegex the subject must match, for example ^(?![A-Z]).+$
subjectPatternErrorCustom message for subjectPattern, with {subject} and {title}
headerPattern, headerPatternCorrespondenceParse a non-standard title format into type, scope and subject
ignoreLabelsSkip validation when the pull request carries one of these labels
wipTreat [WIP] titles as pending instead of failing
validateSingleCommitAlso validate the commit message when there is exactly one non-merge commit
validateSingleCommitMatchesPrTitleRequire that commit's first line to equal the title
githubBaseUrlAPI URL; defaults to ${{ github.api_url }}

Permissions, and the WIP check that needs more

Without wip, the action only reads, and pull-requests: read is the whole permission set. With wip: true it also posts a commit status named action-semantic-pull-request on the head commit: pending while the title starts with [WIP] , success once it validates. That call goes to the commit statuses API, so the job needs statuses: write. The README asks for pull-requests: write; issues #233 and #249 are users finding that statuses: write is what clears Resource not accessible by integration.

Draft pull requests do the same job natively, so on any plan that has them, prefer drafts over wip.

.github/workflows/pr-title.yml
permissions:
  pull-requests: read
  statuses: write
jobs:
  title:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@2252a14e810aa9b85c2d560bd7070c802206ecf6 # v6.1.2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          wip: true
          ignoreLabels: |
            dependencies

Single-commit pull requests

When a pull request has exactly one commit, a squash merge has historically suggested that commit's message instead of the title, so a valid title can still land as wip on the default branch. validateSingleCommit: true validates the commit message as well, ignoring merge commits when counting, and fails with Pull request has only one commit and it's not semantic; this may lead to a non-semantic commit in the base branch. validateSingleCommitMatchesPrTitle goes further and requires the commit's first line to equal the title.

The README now calls both options legacy. Since May 2022 a repository can make squash merges default to the pull request title, which removes the problem at the source; set that and leave both options off.

From comma lists to one entry per line

Moving from v5 to v6 changes the runtime and nothing in your configuration; moving from v4 means rewriting comma separated lists as one entry per line.

ReleaseChange
v5.0.0 (2022-10-11)List inputs newline separated instead of comma separated
v6.0.0 (2025-08-13)Node 24 and ESM
v6.1.0 (2025-08-19)types entries accept regular expressions
v6.1.2 (2026-11-05)Accumulated dependency updates

Key takeaways

  • The action only reads the title through the API, so pull-requests: read is enough unless you turn on wip.
  • wip: true writes a commit status and needs statuses: write, which a fork run on pull_request cannot get.
  • GitHub blocks pull_request_target by default in public repositories from December 2, 2026; prefer pull_request.
  • Set squash merges to default to the pull request title and the single-commit options become unnecessary.

Frequently asked questions

Is pull_request_target required for forks?
Not for the title check itself. A fork's run on pull_request gets a read-only token, which is enough to read the pull request, and issue #219 reports such a run passing. You need write access only for wip, which posts a commit status.
Why is GITHUB_TOKEN necessary?
The action fetches the pull request through the REST API to get the current title, because the event payload can be out of date after an edit and a re-run. That call needs a token, and pull-requests: read covers it. The maintainer gave this reason in issue #218.
Why does action-semantic-pull-request fail with Resource not accessible by integration?
Almost always because wip: true is set and the token cannot write commit statuses. Add statuses: write to the job's permissions, as the reporters in issues #233 and #249 found, or drop wip and use draft pull requests.
Is it a security concern to use pull_request_target with this action?
Not as published, since the job never checks out or runs the fork's code; the maintainer made the same point in issue #182. It becomes one if a checkout of the pull request head and a build step are added to that job, and in public repositories GitHub's default policy blocks the event from December 2, 2026.

References

A title check on a VM no other job touches. Latchkey runners. Start free → 30-day trial · No credit card