Guide

OpenAPI diff in CI that never blocks the build

Last checked 2026-11-098 min read

A non-blocking OpenAPI diff in CI is a pipeline step that compares the spec in a pull request with the spec on the target branch, reports breaking changes where reviewers will see them, and never fails the pipeline. It is the easy way to start: nobody’s merge is held up while you learn what the check finds. Below are copyable setups for GitHub Actions and GitLab CI using the free oasdiff tool, the three ways a “non-blocking” check still ends up blocking, and the questions a security review will ask about any step that reads your spec. Graphwake’s CI step works the same way and goes one step further: it tells the teams that call each changed endpoint.

ExampleGitHub Actions · pull request #1284
  • Check out code2s
  • Run tests41s
  • Graphwake spec check1s
  • graphwake: spec sent (pull request #1284)
  • Build1m 12s
Build passed
In Graphwake: impact 79 HighThe teams that consume the endpoints were told.

If Graphwake can’t be reached, the step logs it and exits cleanly. The build carries on.

One step sends the spec. It never blocks a build. GitHub Actions, GitLab CI, Azure DevOps, Bitbucket Pipelines and Jenkins.
On this page
  1. Why start non-blocking
  2. Three ways a “non-blocking” check still blocks
  3. GitHub Actions
  4. GitLab CI
  5. What a security review will ask
  6. Rolling it out
  7. Where Graphwake fits
  8. When a blocking check alone is enough
  9. Next steps
  10. Questions

Why start non-blocking

  • Some breaking changes are intended. A check that fails the build makes the author fight the tool instead of telling the consumers.
  • The first weeks are noisy. You will find rules you want to ignore and specs that do not parse. Better to learn that from a report than from a blocked release.
  • It is easier to approve. A step that cannot stop a pipeline is a small change to review. Platform and security teams say yes faster.
  • You can tighten it later. Turning a report into a gate is one line. Going the other way after people have learned to override it is harder.

Three ways a “non-blocking” check still blocks

CauseWhat happensFix
A fail threshold--fail-on ERR (or the action’s fail-on input) exits 1 on a breaking change.Leave the threshold out, or let the CI ignore the exit code.
The tool cannot runA spec that does not parse, a file missing on one side, or a blocked external $ref: oasdiff exits with 2 or higher (102 for a spec it cannot load), with or without a threshold.Tell the CI to carry on when the step fails: continue-on-error in GitHub Actions, allow_failure in GitLab CI.
The merge rulesOn GitHub, a required status check blocks the merge whenever the job fails. On GitLab, “Pipelines must succeed” blocks it when a job fails without allow_failure.Do not make the spec check required while it is in report-only mode, and keep the carry-on setting from the row above.
See who your next breaking change reaches.Paste a spec URL or add one CI step. 7-day free trial. Cancel before day 7 and you pay nothing.
Start free trial

A step that calls a hosted service has a fourth way: the service is down or slow, and the step waits or fails. Any such step should time out and exit cleanly. That is the first thing to check before you add one.

GitHub Actions

The oasdiff GitHub Action without fail-on reports and passes. continue-on-error covers the case where oasdiff cannot run:

yaml
name: api-spec-check
on:
  pull_request:
    paths: [ "openapi.yaml" ]
permissions:
  contents: read
jobs:
  spec-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: git fetch --depth=1 origin ${{ github.base_ref }}
        continue-on-error: true
      - uses: oasdiff/oasdiff-action/breaking@v0
        continue-on-error: true
        with:
          base: 'origin/${{ github.base_ref }}:openapi.yaml'
          revision: 'HEAD:openapi.yaml'
          review: false
.github/workflows/api-spec-check.yaml. Action inputs from the oasdiff-action README, checked 2026-11-09.
  1. No fail-on

    Breaking changes appear as annotations on the Files changed tab and in the run summary. The job stays green.

  2. continue-on-error on two steps

    If the base branch cannot be fetched or the spec does not parse, the step fails but the job passes. In later steps, steps.<id>.outcome is failure while conclusion is success, if you want to report that somewhere.

  3. review: false

    By default the action uploads both specs to oasdiff.com, encrypted in CI, for a side-by-side review link. With false, no spec leaves the runner. Drop the line if you want the link.

  4. Minimal permissions

    contents: read only. The action needs pull-requests: write just to comment the review link, which this setup does not post.

GitLab CI

oasdiff publishes a Docker image with git included, so the job needs no install step. The merge request’s base commit comes from a predefined variable:

yaml
api-spec-diff:
  stage: test
  image:
    name: tufin/oasdiff:v1.37.0
    entrypoint: [""]
  variables:
    GIT_DEPTH: "0"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - openapi.yaml
  script:
    - oasdiff breaking "$CI_MERGE_REQUEST_DIFF_BASE_SHA:openapi.yaml" HEAD:openapi.yaml --format junit > oasdiff-junit.xml
    - oasdiff breaking "$CI_MERGE_REQUEST_DIFF_BASE_SHA:openapi.yaml" HEAD:openapi.yaml --fail-on ERR --color never
  allow_failure: true
  artifacts:
    when: always
    reports:
      junit: oasdiff-junit.xml
A job for .gitlab-ci.yml. Keywords checked on the GitLab CI/CD YAML reference, 2026-11-09. tufin/oasdiff is the image the oasdiff Action itself builds on.
  1. entrypoint: [""]

    The image’s entrypoint is the oasdiff binary. GitLab needs a shell to run script, so the entrypoint is cleared.

  2. GIT_DEPTH: "0" and the base SHA

    CI_MERGE_REQUEST_DIFF_BASE_SHA is the commit the merge request diff starts from. A full clone makes sure that commit is there. Comparing with it, not with the tip of the target branch, keeps other merged work out of the diff. In merged-results pipelines, compare with $CI_MERGE_REQUEST_TARGET_BRANCH_SHA instead.

  3. JUnit report

    The first command writes each breaking change as a failed test case. GitLab shows them in the merge request’s test report, so reviewers do not have to open the job log.

  4. --fail-on ERR with allow_failure: true

    An error-level change makes the job fail, and allow_failure turns that into an orange warning while the pipeline still passes. The same happens if oasdiff cannot run. Reviewers see something is off; nobody is stopped.

For a plain green job, drop the second command and keep allow_failure: true for the cases where oasdiff cannot run. On other CI systems the rule is the same: run the diff without a threshold, and tell the CI to carry on if the step itself fails.

What a security review will ask

Any step that reads your API spec in CI gets the same questions. Have the answers ready:

QuestionThe setups above
What does the step read?The spec file at two commits. oasdiff never runs your service or reads your code.
Does anything leave the runner?Not with review: false on GitHub. The GitLab job sends nothing; it pulls only the image.
Which permissions does it need?Read access to the repository. No secrets, no write scopes.
Can a pull request make it fetch URLs?The action does not resolve external $refs unless allow-external-refs: true is set. The CLI resolves them by default; add --allow-external-refs=false for untrusted merge requests.
What happens when it breaks?The pipeline carries on: continue-on-error or allow_failure.
Is the version pinned?The image tag is. @v0 on GitHub follows every stable v0 release; pin a release tag if you want to review each upgrade.

Rolling it out

  1. Week one: report only

    Add the job without a threshold on one API. Nobody is blocked, and you see what it finds on real pull requests.

  2. Tune the noise

    Fix specs that do not parse. Put agreed breaking changes in an ignore file, each with the pull request that accepted it.

  3. Add a visible signal

    On GitLab, the --fail-on ERR line with allow_failure gives an orange job. On GitHub, the annotations are the signal.

  4. Decide about a gate, if ever

    If a breaking change should stop a merge until someone approves it, make that a separate, explicit decision and mark the check required.

Where Graphwake fits

A diff in the pull request tells the author and the reviewers. It does not tell the teams that call the endpoint. Graphwake’s CI step sends the spec on every pull request; Graphwake finds the breaking changes and alerts the teams subscribed to the changed endpoints. The step follows the rules above by design.

  • Never fails a build

    The CI step never fails or blocks a build, whatever it finds.

  • Exits cleanly when we are down

    If Graphwake can’t be reached, the step logs it and exits cleanly. The build carries on.

  • Reads the spec, not your code

    The step sends your OpenAPI spec. Graphwake never reads your code and needs no repository access.

  • One line in the log

    On a pull request it prints graphwake: spec sent (pull request #1284). Pull request specs are stored as drafts and do not replace the live spec.

It runs in GitHub Actions, GitLab CI, Azure DevOps, Bitbucket Pipelines and Jenkins, next to an oasdiff job if you keep one. If you would rather not touch CI at all, give Graphwake your spec’s URL and it checks it every 30 minutes.

Good to know

When a blocking check alone is enough

  • Your API is public or contractual, and no breaking change may ship without an explicit approval: set fail-on and make the check required.
  • One team owns the API and every caller, so a red build reaches everyone who needs to know.

Next steps

Questions

How do I run oasdiff in CI without failing the build?

Leave out --fail-on (the action’s fail-on input), and tell the CI to carry on if the step itself fails: continue-on-error: true in GitHub Actions, allow_failure: true in GitLab CI. oasdiff still exits non-zero when it cannot load a spec.

Can a GitLab job show a warning without failing the pipeline?

Yes. A failing job with allow_failure: true shows an orange warning and the pipeline passes. Use --fail-on ERR so the warning means “breaking change found”.

Does an OpenAPI diff in CI need access to my code?

It needs the spec file, which usually lives in the repository the pipeline already checks out. oasdiff compares two spec versions; it does not read or run the code.

Does Graphwake’s CI step block pull requests?

Never. It reports on every pull request, and if Graphwake can’t be reached it logs it and exits cleanly.

Pick an endpoint. See who it reaches. 7-day free trial. Cancel before day 7 and you pay nothing.

Start free trial

Sources and last verified

Sources checked on 2026-11-09.