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

> action-semantic-pull-request checks PR titles against Conventional Commits. Get every v6 input, the right trigger before December 2, and permissions.

Source: https://latchkey.dev/learn/actions/amannn-action-semantic-pull-request  
Updated: 2026-11-12

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

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

> Event behavior and the default policy from docs.github.com, Securely using pull_request_target, read 2026-11-12.

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

> Outputs: `type`, `scope` and `subject`, plus `error_message` on failure. From `action.yml` and `src/` at v6.1.2; the default types are the keys of `conventional-commit-types` 3.0.0, which the action uses. Read 2026-11-12.

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

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

> From the release notes on the action's repository, read 2026-11-12.

## FAQ

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

- [amannn/action-semantic-pull-request README (read 2026-11-12)](https://github.com/amannn/action-semantic-pull-request)
- [action-semantic-pull-request action.yml at v6.1.2](https://github.com/amannn/action-semantic-pull-request/blob/v6.1.2/action.yml)
- [Securely using pull_request_target, docs.github.com](https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target)
- [Issue #219: Is pull_request_target required for forks](https://github.com/amannn/action-semantic-pull-request/issues/219)
- [Issue #233: missing permission for statuses: write](https://github.com/amannn/action-semantic-pull-request/issues/233)

---

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
