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.

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.
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
uipull_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_request | pull_request_target | |
|---|---|---|
| Workflow file used | From the pull request | From the default branch |
| A fork's run gets | Read-only token, no secrets | Write-capable token and secrets |
| A config change in the pull request applies | Immediately | Only after it merges |
wip: true on fork pull requests | Fails without write access | Works |
| Public repositories from December 2, 2026 | Runs | Blocked 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.
| Input | What it does |
|---|---|
types | Allowed types, each a regex wrapped in ^ $ (regex since v6.1.0). Default: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert |
scopes | Allowed scopes as regexes; unset allows any scope |
requireScope | Fail when the title has no scope |
disallowScopes | Scopes that fail the check |
subjectPattern | Regex the subject must match, for example ^(?![A-Z]).+$ |
subjectPatternError | Custom message for subjectPattern, with {subject} and {title} |
headerPattern, headerPatternCorrespondence | Parse a non-standard title format into type, scope and subject |
ignoreLabels | Skip validation when the pull request carries one of these labels |
wip | Treat [WIP] titles as pending instead of failing |
validateSingleCommit | Also validate the commit message when there is exactly one non-merge commit |
validateSingleCommitMatchesPrTitle | Require that commit's first line to equal the title |
githubBaseUrl | API 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.
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: |
dependenciesSingle-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.
| Release | Change |
|---|---|
| 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: readis enough unless you turn onwip. wip: truewrites a commit status and needsstatuses: write, which a fork run onpull_requestcannot get.- GitHub blocks
pull_request_targetby default in public repositories from December 2, 2026; preferpull_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?
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?
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?
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.