Skip to content

Usage

Diff current specs against the last snapshot and classify every change as breaking, non-breaking, or patch-level. Exits 1 if breaking changes are found — use this command in CI to gate merges.

Terminal window
contractual breaking [--contract <name>] [--format <fmt>] [--base <ref>]

contractual breaking compares the current state of each registered contract against its last versioned snapshot stored in .contractual/snapshots/. For each detected change, Contractual classifies it as one of:

  • breaking: requires a major version bump (for example, removing a field, changing a type, deleting an endpoint).
  • non-breaking: requires a minor version bump (for example, adding an optional field, adding a new endpoint).
  • patch: backward-compatible fix, requires a patch version bump.

The command exits with code 1 if any breaking changes are found, which can be used to fail a CI check and block a merge.


FlagDescriptionDefault
--contract <name>Run the diff only for the named contract. Repeat to target multiple contracts.All contracts
--format <fmt>Output format. One of text, json.text
--base <ref>Snapshot tag or git ref to use as the comparison base.Latest versioned snapshot

terminal
$ contractual breaking

Comparing contracts against last snapshot...

payments-api  (openapi)
  [breaking]      DELETE /v1/charges/{id}  Endpoint removed
  [breaking]      GET /v1/charges          Response field `currency` removed
  [non-breaking]  GET /v1/charges          Response field `metadata` added

user-schema  (json-schema)
  No changes detected.

2 breaking change(s) found across 1 contract(s).
terminal
$ contractual breaking --contract payments-api

Comparing payments-api against last snapshot...

  [breaking]      DELETE /v1/charges/{id}  Endpoint removed
  [breaking]      GET /v1/charges          Response field `currency` removed
  [non-breaking]  GET /v1/charges          Response field `metadata` added

2 breaking change(s) found.
terminal
$ contractual breaking --base v1.2.0

Comparing contracts against snapshot v1.2.0...

payments-api  (openapi)
  [breaking]  GET /v1/charges  Response field `currency` removed

1 breaking change(s) found.
terminal
$ contractual breaking --format json

{
"contracts": [
  {
    "name": "payments-api",
    "type": "openapi",
    "changes": [
      {
        "classification": "breaking",
        "description": "Endpoint removed",
        "location": "DELETE /v1/charges/{id}"
      },
      {
        "classification": "breaking",
        "description": "Response field `currency` removed",
        "location": "GET /v1/charges"
      },
      {
        "classification": "non-breaking",
        "description": "Response field `metadata` added",
        "location": "GET /v1/charges"
      }
    ]
  },
  {
    "name": "user-schema",
    "type": "json-schema",
    "changes": []
  }
],
"summary": { "breaking": 2, "nonBreaking": 1, "patch": 0 }
}

CodeMeaning
0No breaking changes detected.
1One or more breaking changes were found.
2Configuration error, for example contractual.yaml not found, unknown contract name, or snapshot not found for the requested --base.
3Tool error, the configured differ binary was not found or exited unexpectedly.

Contractual ships with built-in structural differs for all supported contract types. Override them when:

  • Your team already uses a custom differ tool
  • You have an internal tool that produces structured output
  • You want to add flags or configuration the default invocation does not expose
  • You want to disable breaking change detection for a specific contract

Use the breaking field in the contract configuration. Use {old} for the snapshot path (the baseline) and {new} for the current spec path.

contractual.yaml
contracts:
- name: orders-api
type: openapi
path: ./specs/orders.openapi.yaml
breaking: "my-custom-differ breaking {old} {new} --format json"

Both {old} and {new} are absolute paths. The command must exit with code 0 if no breaking changes are found, and a non-zero code if breaking changes are found.

PlaceholderReplaced withAvailable in
{old}Absolute path to the snapshot (baseline) specbreaking
{new}Absolute path to the current spec filebreaking
{name}The contract’s name valuebreaking

Set breaking: false to skip the step entirely for a contract.

contracts:
- name: internal-config
type: json-schema
path: ./config/internal.schema.json
breaking: false # Internal schema, no breaking change tracking needed

Setting a field to false is different from omitting it. Omitting uses the built-in default. false explicitly opts out.

Contractual runs the custom command and inspects two things:

Exit code: zero means pass, non-zero means failure. This is the primary signal.

Standard output: Contractual attempts to parse the output to extract structured information for PR comments and changeset generation. For the best results, use --format json with tools that support it.


.github/workflows/contracts.yml
name: Contract Governance
on:
pull_request:
paths:
- 'specs/**'
- 'schemas/**'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: contractual-dev/action@v1
with:
mode: pr-check
github-token: ${{ secrets.GITHUB_TOKEN }}
fail-on-breaking: true
Terminal window
npm install -g @contractual/cli
contractual breaking --format json > breaking-report.json
# Fail the build if exit code is 1
if [ $? -eq 1 ]; then
echo "Breaking changes detected"
exit 1
fi