---
title: CI Integration
description: Run Specdiff in your CI pipeline to detect breaking changes and fail the build when needed.
url: https://pr-1-8289d63b6330.thally.app/specdiff/ci-integration
---

# CI Integration

Run Specdiff in your CI pipeline to detect breaking changes and fail the build when needed.

Specdiff integrates with continuous integration pipelines to detect breaking schema changes and block incompatible updates. The CLI returns exit codes that CI systems can act on, and supports formatters for build summaries and PR comments.

## GitHub Actions recipe

Add this workflow to your repository (e.g., `.github/workflows/api-compat.yml`):

```yaml
name: API compatibility
on: pull_request
jobs:
  specdiff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - name: Extract the base branch spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > /tmp/openapi-base.yaml
      - name: Fail on breaking changes
        run: npx -y @specdiff/cli /tmp/openapi-base.yaml openapi.yaml --fail-on breaking --format markdown | tee -a "$GITHUB_STEP_SUMMARY"
```

This workflow:
1. Checks out the PR branch with full history
2. Extracts the spec from the base branch (main, master, etc.)
3. Compares base spec to PR spec
4. Appends the report to the step summary, which appears as a comment on the PR
5. Fails the build if any breaking changes are found

## Exit codes

The `specdiff` CLI returns exit codes that control CI flow:

| Code | Meaning |
|---|---|
| `0` | Success: no changes at or above the `--fail-on` threshold |
| `1` | Threshold exceeded: breaking changes (or other severity) detected |
| `2` | Usage error: invalid flag or argument |
| `3` | Input error: file not found or parse failure |

Use exit code `1` to fail the build and prevent merging:

```bash
specdiff before.yaml after.yaml --fail-on breaking
# Exit code 0: safe to merge
# Exit code 1: stop the build
```

## Output formats

### Text (default)

Human-readable summary grouped by severity:

```bash
specdiff openapi-main.yaml openapi.yaml
```

Use `--color` to force ANSI colors (useful when piping to CI logs):

```bash
specdiff openapi-main.yaml openapi.yaml --color
```

### Markdown for PR comments

Use `--format markdown` to generate a GitHub-Flavored Markdown report and append it to the step summary:

```bash
specdiff openapi-main.yaml openapi.yaml --format markdown >> "$GITHUB_STEP_SUMMARY"
```

This produces:

- A `## Specdiff report (OpenAPI)` or `## Specdiff report (JSON Schema)` heading
- Summary table with counts per severity
- Per-severity tables listing each change

The report appears as a comment on the PR automatically.

### JSON for processing

Export results as JSON for downstream processing:

```bash
specdiff openapi-main.yaml openapi.yaml --format json > report.json
```

Parse the JSON in your CI system to extract counts, paths, or specific breaking changes.

## Controlling the failure threshold

The `--fail-on` flag determines which severity level triggers a non-zero exit code:

| Threshold | Behavior |
|---|---|
| `breaking` (default) | Exit 1 only if breaking changes exist |
| `warning` | Exit 1 if warning or breaking changes exist |
| `info` | Exit 1 if any change exists |
| `none` | Always exit 0 (useful for reporting-only runs) |

Example: fail on warnings instead of just breaking changes:

```bash
specdiff old.yaml new.yaml --fail-on warning
```

## Ignoring specific rules or paths

Skip certain rules that you've decided are acceptable:

```bash
specdiff openapi-main.yaml openapi.yaml \
  --ignore-rule description-changed \
  --ignore-rule deprecated-added
```

Or ignore changes at specific JSON pointers:

```bash
specdiff old.json new.json \
  --ignore-path '#/paths/~1internal' \
  --ignore-path '#/components/schemas/LegacyModel'
```

Use `--ignore-rule` and `--ignore-path` together to refine the report:

```bash
specdiff openapi-main.yaml openapi.yaml \
  --fail-on breaking \
  --ignore-rule description-changed \
  --ignore-path '#/paths/~1internal' \
  --format markdown >> "$GITHUB_STEP_SUMMARY"
```

## Integration with other CI systems

### GitLab CI

```yaml
api_compat:
  image: node:22
  script:
    - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
    - git show origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:openapi.yaml > /tmp/openapi-base.yaml
    - npx -y @specdiff/cli /tmp/openapi-base.yaml openapi.yaml --fail-on breaking --format markdown
```

### Generic bash (any CI system)

```bash
#!/bin/bash
set -e  # fail on first error

# Fetch and extract base spec
git fetch origin main
git show origin/main:openapi.yaml > openapi-base.yaml

# Run Specdiff and capture output
if npx -y @specdiff/cli openapi-base.yaml openapi.yaml \
   --fail-on breaking \
   --format markdown \
   > report.md; then
  echo "✓ No breaking changes"
  cat report.md
else
  echo "✗ Breaking changes detected"
  cat report.md
  exit 1
fi
```

### Saving reports as artifacts

Store the report for later review:

```bash
npx -y @specdiff/cli before.yaml after.yaml \
  --format markdown \
  --output specdiff-report.md
```

Then upload to artifact storage or attach to the build.

## Tips and best practices

- **Run on every PR:** Include Specdiff in your pull request checks to catch schema changes before merge.
- **Use the markdown format:** It renders nicely in PR comments and step summaries, making it easy to spot issues.
- **Set the threshold appropriately:** Use `--fail-on breaking` for strict enforcement; `--fail-on warning` for cautious teams.
- **Document exceptions:** If you ignore a rule or path, add a comment explaining why so future maintainers understand the decision.
- **Version your spec:** Keep version control history of your OpenAPI or JSON Schema files so Specdiff can detect changes.
- **Test locally:** Run Specdiff locally before pushing to catch issues faster.

## Exit code handling

Most CI systems allow you to control flow based on exit codes. Examples:

**GitHub Actions:**
```yaml
- name: Check for breaking changes
  run: npx -y @specdiff/cli before.yaml after.yaml --fail-on breaking
  # If exit code is non-zero, the step fails and the job stops
```

**GitLab CI:**
```yaml
check_schema:
  script:
    - npx -y @specdiff/cli before.yaml after.yaml --fail-on breaking
  # Non-zero exit stops the pipeline
```

**Jenkins / generic systems:**
```bash
npx -y @specdiff/cli before.yaml after.yaml --fail-on breaking
if [ $? -ne 0 ]; then
  echo "Build failed: breaking changes detected"
  exit 1
fi
```