CI/CD integration with the Buf GitHub Action#
The Buf GitHub Action (bufbuild/buf-action) runs the Buf CLI inside a GitHub workflow.
On a single workflow file, it covers build, lint, format, and breaking change checks for pull requests, pushes named modules to the BSR on Git push, and archives BSR labels when a Git branch is deleted.
That last part is what turns CI from a gate into a publishing pipeline: every accepted schema change can update BSR documentation, generated SDKs, Studio, dependency resolution, and registry-side checks without a separate release job.
For non-GitHub CI systems, see CI/CD setup.
Quickstart#
Add .github/workflows/buf-ci.yaml to your repository:
name: Buf CI
on:
push:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
delete:
permissions:
contents: read
pull-requests: write
jobs:
buf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
# BSR token; required for push and label-archive steps.
token: ${{ secrets.BUF_TOKEN }}
# Set this to install Buf and stop, leaving subsequent steps to call buf directly.
# setup_only: true
# Optional GitHub token to avoid rate limits.
# github_token: ${{ secrets.GITHUB_TOKEN }}
The default configuration:
- Runs
build,lint,format, andbreakingchecks on pull requests, with a summary comment on each. - Runs
buf pushon pushes to the repository (commits, branches, and tags), publishing every named module to the BSR with the matching Git metadata. - Archives BSR labels when a Git branch or tag is deleted.
Authentication#
BSR token#
The push and label-archive steps need a BSR token.
Pass it via the token parameter, sourced from a GitHub secret:
A post-action step runs buf registry logout so the token doesn’t linger in ~/.netrc after the job ends.
GitHub token#
The action calls the GitHub API for PR comments and breaking-change comparison; github_token defaults to ${{ github.token }}, so on standard workflows you don’t need to set anything. Pass it explicitly if you want to use a different token (for example, a PAT with broader permissions):
On GitHub Enterprise Server, public_github_token is a separate parameter that supplies a github.com token used to resolve and download Buf CLI release artifacts. It isn’t a substitute for github_token.
What runs when#
The action’s defaults map events to steps:
| Event | Default steps |
|---|---|
pull_request (including from forks) |
build, lint, format, breaking |
pull_request from a non-fork |
Same as above plus a summary comment |
push |
build plus push (the BSR buf push of named modules) |
delete (Git branch or tag) |
Archive matching BSR labels |
pull_request with the buf skip breaking label |
Same as pull_request but without the breaking step |
build always runs (it isn’t gated by a parameter); the per-step toggles below override the rest.
PR-comment steps are skipped automatically on pull requests from forks so the workflow doesn’t fail on the missing token. Don’t enable push or archive on fork PRs: doing so requires exposing a BSR token to untrusted contributors, which isn’t supported.
Customize behavior#
The Action exposes a parameter for each capability. The patterns below cover the common cases; for the full surface, see Parameters.
Pin the Buf CLI version#
For reproducible builds, set version:
If you don’t set version, the action resolves in this order: the BUF_VERSION environment variable, any buf already installed on the runner, then the latest release on GitHub.
Specify an input directory#
For repositories where buf.yaml lives in a subdirectory, set input:
By default breaking compares against the same subdirectory at the comparison ref.
To override, set breaking_against:
- uses: bufbuild/buf-action@v1
with:
input: <path/to/module>
breaking_against: ${{ github.event.repository.clone_url }}#format=git,commit=${{ github.event.pull_request.base.sha }},subdir=<path/to/module>
Or check out the base branch into a separate path and point at it locally:
- uses: actions/checkout@v4
with:
path: head
- uses: actions/checkout@v4
with:
path: base
ref: ${{ github.event.pull_request.base.sha }}
- uses: bufbuild/buf-action@v1
with:
input: head/<path/to/module>
breaking_against: base/<path/to/module>
For the full input grammar, see Inputs.
Run setup only#
To install the Buf CLI without running any of the action’s checks (so subsequent steps can call buf directly), set setup_only: true:
- uses: bufbuild/buf-action@v1
with:
setup_only: true
- run: buf build --error-format github-actions
See the only-setup example for a full configuration.
Skip breaking-change detection#
The default workflow checks for a buf skip breaking label on the pull request and skips the breaking step when it’s present.
This makes intentional breaks visible and reviewable.
To enable the label-based skip, add a buf skip breaking label to the repository (in Issues > Labels, or directly on a PR via Edit labels) and keep the default pull_request event types in the workflow:
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
To opt out of label-based skipping, override breaking so it depends only on the event type:
See examples/disable-skip/buf-ci.yaml for the full setup.
For a commit-message-based skip instead of a PR label, condition breaking on head_commit.message:
- uses: bufbuild/buf-action@v1
with:
breaking: |
contains(fromJSON('["push", "pull_request"]'), github.event_name) &&
!contains(github.event.head_commit.message, 'buf skip breaking')
Disable specific steps#
The opt-out steps (lint, format, breaking, push, archive, and pr_comment) each have a boolean parameter.
Set it to false to disable that step. build always runs and isn’t gated by a parameter.
Customize per-step triggers#
To run a step on additional events, set its parameter to a GitHub Actions expression that evaluates to a boolean.
For example, to run format on both push and pull request:
- uses: bufbuild/buf-action@v1
with:
format: ${{ contains(fromJSON('["push", "pull_request"]'), github.event_name) }}
Push only when APIs change#
To skip pushes that don’t touch Buf-related files, restrict the push event itself with a paths filter:
push:
paths:
- '**.proto'
- '**/buf.yaml'
- '**/buf.lock'
- '**/buf.md'
- '**/README.md'
- '**/LICENSE'
See the push-on-changes example for the full configuration.
Configure summary comment#
The action posts a comment on each pull request summarizing the latest check results:
The comment is keyed by <workflow>:<job>.
If two workflows share the same workflow-and-job names, one run’s comment will overwrite the other’s; give them distinct workflow or job names if you need both visible.
To disable the comment, set pr_comment: false and drop the pull-requests: write permission, since the action no longer needs it:
name: Buf CI
on:
push:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
delete:
permissions:
contents: read
- pull-requests: write
jobs:
buf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bufbuild/buf-action@v1
with:
token: ${{ secrets.BUF_TOKEN }}
+ pr_comment: false
Parameters#
Set parameters under the with: block of the bufbuild/buf-action step.
Setup#
| Parameter | Description | Default |
|---|---|---|
version |
Buf CLI version to install. | Latest release |
setup_only |
Install Buf and stop, without running any check. | false |
Authentication#
| Parameter | Description | Default |
|---|---|---|
token |
BSR token for push and label-archive steps. |
none |
github_actor |
GitHub actor for API requests. | Actor from the GitHub context |
github_token |
GitHub token for API requests (avoids rate limits). | Token from the GitHub context |
Input selection#
| Parameter | Description | Default |
|---|---|---|
input |
Input for the buf command. |
none (repository root) |
paths |
Limit to specific files or directories (newline-separated). | none |
exclude_imports |
Exclude files imported by the target modules. | false |
exclude_paths |
Exclude specific files or directories (newline-separated). | none |
Step toggles#
Each toggle defaults to a sensible event filter; override with false to disable, or with a GitHub Actions expression for a custom condition.
| Parameter | Description | Default behavior |
|---|---|---|
lint |
Run buf lint. |
Runs on pull requests |
format |
Run buf format and report diffs. |
Runs on pull requests |
breaking |
Run breaking-change detection. | Runs on pull requests, skipped when the buf skip breaking label is present |
push |
Run buf push of named modules. |
Runs on pushes from non-fork branches |
archive |
Archive BSR labels matching deleted Git refs. | Runs on delete events from non-fork branches |
pr_comment |
Post a summary comment on the PR. | Posts on non-fork pull requests |
Breaking comparison#
| Parameter | Description | Default |
|---|---|---|
breaking_against |
Input to compare against. | Base of the PR (or the commit before a push) |
breaking_against_registry |
Use the BSR for the comparison instead of breaking_against. |
false |
Push and archive#
| Parameter | Description | Default |
|---|---|---|
push_create_visibility |
Visibility of repositories the action creates: public or private. |
private |
push_disable_create |
Don’t auto-create BSR repositories on push. | false |
Advanced#
| Parameter | Description | Default |
|---|---|---|
checksum |
sha256 of the Buf CLI binary to verify after download. | none |
domain |
BSR host for login (set this for private BSR instances). | buf.build |
public_github_token |
Token for https://github.com requests when running on a private GitHub Enterprise instance. |
none |
Migrate from the individual actions#
If you’re using the previous individual actions (buf-setup-action, buf-lint-action, buf-breaking-action, buf-push-action), see bufbuild/buf-action/MIGRATION.md for step-by-step instructions.
The unified action covers everything those four did and adds PR summary comments, label-based skip-breaking, automatic Git metadata on pushes, and label-archive on branch delete.
The individual actions still work but will be removed in a future release. For an overview of the old action layout and a high-level mapping, see GitHub Actions (deprecated).
Debugging#
Re-run the workflow with debug logging enabled; the action passes --debug to the main buf invocations (lint, format, breaking, push, archive) when GitHub debug logging is on.


