Skip to content

delimit-ai/delimit-action

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

</> Delimit GitHub Action

Catch breaking API changes before merge — semver classification, migration guides, and policy enforcement for OpenAPI specs.


Think and Build

Delimit is more than just an action — it is a governance layer for your AI coding assistants. When you use the Delimit Swarm, you can say "Think and Build" to your agents, and they will automatically use this action to verify their own code changes against your API policies before requesting a merge.

GitHub Marketplace License: MIT API Governance

Delimit runs on every pull request, compares your OpenAPI spec against the base branch, and posts a detailed comment with breaking changes, semver classification, policy violations, and migration guidance. No API keys, no external services, no config required to get started.

What it looks like

Delimit PR comment showing breaking changes


Features

  • Breaking change detection — catches 27 types of changes (17 breaking, 10 non-breaking) across endpoints, parameters, response schemas, types, enums, security, and constraints
  • Semver classification — deterministic major / minor / patch / none bump recommendation with computed next version
  • Migration guides — auto-generated step-by-step migration instructions for every breaking change
  • PR comments — rich Markdown summary posted directly on your pull request, updated on each push
  • Advisory and enforce modes — start with non-blocking warnings, promote to CI-gating when ready
  • Custom policies — define your own governance rules in .delimit/policies.yml with path patterns, severity levels, and custom messages
  • 7 explainer templates — developer, team lead, product, migration, changelog, PR comment, and Slack formats

Quick Start

Add this file to .github/workflows/api-check.yml:

name: API Contract Check
on: pull_request

jobs:
  delimit:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: delimit-ai/delimit-action@v1
        with:
          spec: api/openapi.yaml

That is it. Delimit auto-fetches the base branch version of your spec and diffs it against the PR changes. Runs in advisory mode by default — posts a PR comment but never fails your build.

What the PR comment looks like

When Delimit detects breaking changes, it posts a comment like this:

Delimit API Governance | Breaking Changes Detected

Change Path Severity
endpoint_removed DELETE /pets/{petId} error
type_changed /pets:GET:200[].id (string → integer) warning
enum_value_removed /pets:GET:200[].status warning

Semver: MAJOR (1.0.0 → 2.0.0)

Migration Guide (3 steps)

Step 1: DELETE /pets/{petId} was removed. Update clients to use an alternative endpoint or remove calls to this path.

Step 2: id changed from string to integer. Update serialization logic, type assertions, and database column types.

Step 3: status enum value "pending" was removed. Update clients to stop sending this value.

See the live demo — a Users API migration with 23 breaking changes detected across 27 change types, severity badges, and a migration guide.

Advanced: explicit base and head specs

If you need to compare specific files (e.g., pre-checked-out base branch), use old_spec and new_spec instead:

      - uses: delimit-ai/delimit-action@v1
        with:
          old_spec: base/api/openapi.yaml
          new_spec: api/openapi.yaml

Full Usage

name: API Governance
on: pull_request

jobs:
  api-check:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4

      - name: Checkout base spec
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}
          path: base

      - uses: delimit-ai/delimit-action@v1
        id: delimit
        with:
          old_spec: base/api/openapi.yaml
          new_spec: api/openapi.yaml
          mode: enforce
          policy_file: .delimit/policies.yml
          github_token: ${{ secrets.GITHUB_TOKEN }}

      - name: Use outputs
        if: always()
        run: |
          echo "Breaking changes: ${{ steps.delimit.outputs.breaking_changes_detected }}"
          echo "Violations: ${{ steps.delimit.outputs.violations_count }}"
          echo "Semver bump: ${{ steps.delimit.outputs.semver_bump }}"
          echo "Next version: ${{ steps.delimit.outputs.next_version }}"

Inputs

Input Required Default Description
spec No '' Path to the changed OpenAPI or JSON Schema spec. On pull requests, Delimit auto-fetches the base branch version for comparison.
old_spec No '' Path to the old/base API specification file.
new_spec No '' Path to the new/changed API specification file.
mode No advisory advisory (comments only) or enforce (fails CI on breaking changes).
fail_on_breaking No false Boolean alias for mode: enforce. When true, fails CI on breaking changes regardless of mode.
github_token No ${{ github.token }} GitHub token used to post PR comments.
policy_file No '' Path to a custom policy file (e.g., .delimit/policies.yml).
webhook_url No '' Slack or Discord webhook URL. Delimit posts a notification when breaking changes are detected. Auto-detects the platform from the URL.
generator_command No '' Optional shell command that regenerates a generated artifact (e.g. pnpm run schema:export). When set, Delimit runs this command in a sandbox and diffs the regenerated output against the committed artifact to detect drift between source-of-truth and committed file. Pair with generator_artifact. See Generator drift detection.
generator_artifact No '' Path to the generated artifact that generator_command produces (e.g. schemas/v1/agent.schema.json). Required when generator_command is set.

Note: Provide either spec for pull request workflows, or both old_spec and new_spec for explicit comparisons. If neither form is provided, the action exits with an error.


Generator drift detection

Many repos commit a JSON Schema (or similar artifact) that is generated from a source-of-truth file — for example a Zod schema in TypeScript compiled to JSON Schema via zodToJsonSchema, or a Protobuf file compiled to OpenAPI. A common class of bug is that someone updates the source and forgets to regenerate the committed artifact, so the two drift apart silently.

Delimit can catch this on every PR by running the generator in a sandbox and diffing its output against the committed file.

- uses: delimit-ai/delimit-action@v1
  with:
    spec: schemas/v1/agent.schema.json
    generator_command: pnpm run schema:export
    generator_artifact: schemas/v1/agent.schema.json

On every PR that touches the schema:

  1. Delimit runs generator_command in a sandboxed copy of the working tree.
  2. It reads the regenerated artifact and diffs it against the committed file.
  3. Any drift is reported in the PR comment, classified using the same JSON Schema semantics as normal schema changes (property add/remove, required, type widen/narrow, enum, const, additionalProperties, pattern, length and numeric bounds).
  4. The committed file is restored before the workflow exits — the working tree is never modified.

If the regenerated output matches the committed artifact exactly, no drift is reported and the check passes silently. If the generator fails (non-zero exit or missing output file), Delimit reports the failure as an advisory warning and continues with the normal schema diff.

This is separate from the base-branch schema diff. Both run on the same PR and are reported in the same comment:

  • Schema classification — committed JSON Schema vs base branch (what changed in this PR)
  • Generator drift — regenerated artifact vs committed file in this PR (is the committed file stale)

You can use either independently, or both together. generator_command is opt-in — leave it empty to skip the drift check entirely.

Supported generators

Anything that produces a JSON Schema or OpenAPI file at a known path and exits with code 0. Common examples:

# Zod → JSON Schema via zodToJsonSchema
generator_command: pnpm run schema:export
generator_artifact: schemas/v1/agent.schema.json

# Protobuf → OpenAPI via buf or protoc-gen-openapi
generator_command: buf generate
generator_artifact: gen/openapi/api.yaml

# TypeBox → JSON Schema
generator_command: npm run build:schema
generator_artifact: dist/schema.json

The action needs whatever toolchain the generator depends on to already be installed in the workflow — add actions/setup-node, pnpm/action-setup, or equivalent steps before the Delimit step.


Outputs

Output Type Description
breaking_changes_detected string "true" if any breaking change was found, "false" otherwise.
violations_count string Number of policy violations (errors + warnings).
semver_bump string Recommended version bump: major, minor, patch, or none.
next_version string Computed next version string (e.g., 2.0.0).
report string Full JSON report of all detected changes, violations, and semver data.

Using outputs in subsequent steps

- uses: delimit-ai/delimit-action@v1
  id: delimit
  with:
    old_spec: base/api/openapi.yaml
    new_spec: api/openapi.yaml

- name: Block release on breaking changes
  if: steps.delimit.outputs.breaking_changes_detected == 'true'
  run: |
    echo "Breaking changes detected — semver bump: ${{ steps.delimit.outputs.semver_bump }}"
    echo "Next version should be: ${{ steps.delimit.outputs.next_version }}"
    exit 1

- name: Auto-tag on minor bump
  if: steps.delimit.outputs.semver_bump == 'minor'
  run: |
    git tag "v${{ steps.delimit.outputs.next_version }}"

Custom Policies

Create .delimit/policies.yml in your repository root to define governance rules beyond the defaults.

# .delimit/policies.yml

# Set to true to replace all default rules with only your custom rules.
# Default: false (custom rules merge with defaults).
override_defaults: false

rules:
  # Forbid removing endpoints without deprecation
  - id: no_endpoint_removal
    name: Forbid Endpoint Removal
    change_types:
      - endpoint_removed
    severity: error      # error | warning | info
    action: forbid       # forbid | allow | warn
    message: "Endpoint {path} cannot be removed. Use deprecation headers instead."

  # Protect V1 API — no breaking changes allowed
  - id: protect_v1_api
    name: Protect V1 API
    description: V1 endpoints are frozen
    change_types:
      - endpoint_removed
      - method_removed
      - field_removed
    severity: error
    action: forbid
    conditions:
      path_pattern: "^/v1/.*"
    message: "V1 API is frozen. Changes must be made in V2."

  # Warn on type changes in 2xx responses
  - id: warn_response_type_change
    name: Warn Response Type Changes
    change_types:
      - type_changed
    severity: warning
    action: warn
    conditions:
      path_pattern: ".*:2\\d\\d.*"
    message: "Type changed at {path} — verify client compatibility."

  # Allow adding enum values (informational)
  - id: allow_enum_expansion
    name: Allow Enum Expansion
    change_types:
      - enum_value_added
    severity: info
    action: allow
    message: "Enum value added (non-breaking)."

Available change types for rules

Change type Breaking Description
endpoint_removed Yes An API endpoint path was removed
method_removed Yes An HTTP method was removed from an endpoint
required_param_added Yes A new required parameter was added
param_removed Yes A parameter was removed
response_removed Yes A response status code was removed
required_field_added Yes A new required field was added to a request body
field_removed Yes A field was removed from a response
type_changed Yes A field's type was changed (e.g., string to integer)
format_changed Yes A field's format was changed (e.g., date to date-time)
enum_value_removed Yes An allowed enum value was removed
endpoint_added No A new endpoint was added
method_added No A new HTTP method was added to an endpoint
optional_param_added No A new optional parameter was added
response_added No A new response status code was added
optional_field_added No A new optional field was added
enum_value_added No A new enum value was added
description_changed No A description was modified

Default rules

Delimit ships with 6 built-in rules that are always active unless you set override_defaults: true:

  1. Forbid Endpoint Removal — endpoints cannot be removed (error)
  2. Forbid Method Removal — HTTP methods cannot be removed (error)
  3. Forbid Required Parameter Addition — new required params break clients (error)
  4. Forbid Response Field Removal — removing fields from 2xx responses (error)
  5. Warn on Type Changes — type changes flagged as warnings
  6. Allow Enum Expansion — adding enum values is always safe (info)

Slack / Discord Notifications

Get notified in Slack or Discord when breaking API changes are detected. Add a webhook_url input pointing to your channel's incoming webhook:

- uses: delimit-ai/delimit-action@v1
  with:
    spec: api/openapi.yaml
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}

The notification fires only when breaking changes are found. If the webhook URL is not set, this step is silently skipped.

Supported platforms

Platform URL pattern Payload format
Slack hooks.slack.com Block Kit with mrkdwn
Discord discord.com/api/webhooks Rich embed with color and fields
Generic Anything else Plain JSON event payload

Delimit auto-detects the platform from the URL and formats the message accordingly. Webhook failures are logged as warnings but never fail your CI run.

Discord example

- uses: delimit-ai/delimit-action@v1
  with:
    spec: api/openapi.yaml
    webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

Generic webhook

Any URL that is not Slack or Discord receives a JSON payload:

{
  "event": "breaking_changes_detected",
  "repo": "org/repo",
  "pr_number": 123,
  "pr_title": "Update user endpoints",
  "breaking_changes": 3,
  "additive_changes": 1,
  "semver": "MAJOR",
  "pr_url": "https://github.com/org/repo/pull/123"
}

Advisory vs Enforce Mode

Behavior advisory (default) enforce
PR comment Yes Yes
GitHub annotations Yes Yes
Fails CI on breaking changes No Yes
Exit code on violations 0 1

Start with advisory mode. It gives your team visibility into API changes without blocking merges. Once your team is comfortable, switch to enforce to gate deployments.

# Advisory — non-blocking (default)
- uses: delimit-ai/delimit-action@v1
  with:
    old_spec: base/api/openapi.yaml
    new_spec: api/openapi.yaml
    mode: advisory

# Enforce — blocks merge on breaking changes
- uses: delimit-ai/delimit-action@v1
  with:
    old_spec: base/api/openapi.yaml
    new_spec: api/openapi.yaml
    mode: enforce

Examples

Advisory mode (recommended starting point)

name: API Check
on: pull_request

jobs:
  api-check:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}
          path: base
      - uses: delimit-ai/delimit-action@v1
        with:
          old_spec: base/api/openapi.yaml
          new_spec: api/openapi.yaml

Enforce mode

name: API Governance
on: pull_request

jobs:
  api-check:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}
          path: base
      - uses: delimit-ai/delimit-action@v1
        with:
          old_spec: base/api/openapi.yaml
          new_spec: api/openapi.yaml
          mode: enforce

Custom policy file

- uses: delimit-ai/delimit-action@v1
  with:
    old_spec: base/api/openapi.yaml
    new_spec: api/openapi.yaml
    mode: enforce
    policy_file: .delimit/policies.yml

Using outputs to control downstream jobs

jobs:
  api-check:
    runs-on: ubuntu-latest
    outputs:
      breaking: ${{ steps.delimit.outputs.breaking_changes_detected }}
      bump: ${{ steps.delimit.outputs.semver_bump }}
      next_version: ${{ steps.delimit.outputs.next_version }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.base.sha }}
          path: base
      - uses: delimit-ai/delimit-action@v1
        id: delimit
        with:
          old_spec: base/api/openapi.yaml
          new_spec: api/openapi.yaml

  deploy:
    needs: api-check
    if: needs.api-check.outputs.breaking != 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Safe to deploy — next version ${{ needs.api-check.outputs.next_version }}"

Supported Formats

  • OpenAPI 3.0 and 3.1
  • Swagger 2.0
  • YAML and JSON spec files

What the PR Comment Looks Like

When Delimit detects changes, it posts (or updates) a comment on your pull request:

## Delimit: Breaking Changes `MAJOR`

| Metric | Value |
|--------|-------|
| Semver bump | `major` |
| Next version | `2.0.0` |
| Total changes | 5 |
| Breaking | 2 |
| Violations | 2 |

### Violations

| Severity | Rule | Description | Location |
|----------|------|-------------|----------|
| Error | Forbid Endpoint Removal | Endpoint /users/{id} cannot be removed | `/users/{id}` |
| Warning | Warn on Type Changes | Type changed from string to integer | `/users:200.age` |

<details>
<summary>Migration guide</summary>
...step-by-step instructions for each breaking change...
</details>

The comment is automatically updated on each push to the PR branch. No duplicate comments.


FAQ / Troubleshooting

Delimit skipped validation and did nothing

Both old_spec and new_spec must be provided. If either is empty, Delimit exits cleanly with no output. Make sure both paths point to valid spec files.

How do I get the base branch spec?

Use a second actions/checkout step to check out the base branch into a subdirectory:

- uses: actions/checkout@v4
  with:
    ref: ${{ github.event.pull_request.base.sha }}
    path: base

Then reference base/path/to/openapi.yaml as old_spec.

My spec file is not found

Verify the path relative to the repository root. Common locations:

  • api/openapi.yaml
  • docs/openapi.yaml
  • openapi.yaml
  • swagger.json

The action posts duplicate PR comments

Delimit searches for an existing comment containing "Delimit" from a bot user and updates it in place. If you see duplicates, ensure github_token has pull-requests: write permission.

Can I use this with JSON specs?

Yes. Delimit supports both YAML (.yaml, .yml) and JSON (.json) spec files. Set the input paths accordingly.

Can I use this in a monorepo with multiple specs?

Yes. Add multiple Delimit steps, each with different old_spec / new_spec pairs:

- uses: delimit-ai/delimit-action@v1
  with:
    old_spec: base/services/users/openapi.yaml
    new_spec: services/users/openapi.yaml

- uses: delimit-ai/delimit-action@v1
  with:
    old_spec: base/services/billing/openapi.yaml
    new_spec: services/billing/openapi.yaml

Advisory mode still shows errors in the PR comment — is that expected?

Yes. Advisory mode reports everything (including errors) in the PR comment and GitHub annotations, but it always exits with code 0 so your CI stays green. Switch to enforce mode when you want breaking changes to block the merge.

How is the semver bump calculated?

The classification is deterministic:

  • major — any breaking change detected (endpoint removed, required param added, field removed, type changed, etc.)
  • minor — additive changes only (new endpoints, optional fields, enum values added)
  • patch — non-functional changes only (description updates)
  • none — no changes detected

CLI

For local development, pre-commit checks, and CI/CD pipelines outside GitHub Actions, use the Delimit CLI:

npm install -g delimit-cli
delimit lint api/openapi.yaml
delimit diff old-api.yaml new-api.yaml
delimit explain old-api.yaml new-api.yaml --template migration

Links


License

MIT

About

GitHub Action: catch breaking API changes on every PR. Posts severity-graded comments with migration guides. 27 detection types.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages