# Roave Backward Compatibility Check turns an API change into a failed build

> Roave Backward Compatibility Check compares the API of two revisions of a PHP library by reflection and static analysis, and it is built for one job: run it in CI against the last tagged release so that a breaking change fails the build before it reaches a consumer.

**Roave/BackwardCompatibilityCheck** — :ab: Tool to compare two revisions of a class API to check for BC breaks

- Repository: https://github.com/Roave/BackwardCompatibilityCheck
- Stars: 603 · Forks: 68
- Language: PHP
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/roave-backwardcompatibilitycheck

## A tool whose output is a build failure

Roave Backward Compatibility Check is described in one sentence as a tool that can be used to verify BC breaks between two versions of a PHP library, and the repository description calls it a tool to compare two revisions of a class API to check for BC breaks. The topics attach reflection and static analysis to it, which is how it works.

The intended usage is CI, and the shape of it is deliberately minimal. You add the tool to your build and run one command:

```bash
vendor/bin/roave-backward-compatibility-check
```

The tool then detects the last minor version tagged and compares the API against the current HEAD. If any BC breaks are found, it returns a non-zero status, which on most CI systems causes the build to fail. That is the entire product. There is no report to read, no dashboard and no baseline to configure, and a maintainer who merges a renamed method or a changed parameter type finds out from a red build.

That is a much stronger guarantee than a checklist in a contributing guide, because it is enforced on every push rather than at review time, and because the comparison is mechanical rather than a matter of reviewer attention.

The licence is MIT. The README also carries a tip inviting people who need help with the tool, with auditing their code, or with support on legacy applications to get in touch with the Roave team, which tells you the kind of user the project is aimed at.

## The assumptions that decide whether it works for you

The pre-requisites section is short and it is the first thing to read, because each assumption removes a set of possible uses.

Your project uses git. Your project uses composer.json to define its dependencies. All source paths are covered by an autoload section in composer.json. And changes need to be committed to git to be covered, although the documentation adds that you can implement your own logic to extract sources and dependencies from a project.

That last sentence is the one that tells you the real design. The tool does not scan a directory of PHP files and guess which ones are part of the public surface. It resolves the project through Composer's autoload map and then reasons over the classes that map contains, using reflection to see what a class actually exposes. That is why the autoload requirement is stated as an absolute rather than a recommendation, and it is also why the autoload section is the place to draw the line between your public API and your internals.

The git requirement is the other half of the contract. The comparison is between two revisions, and those revisions have to be real, committed states with real tags on them. A working tree with uncommitted changes is outside the model.

The escape hatch, implementing your own logic to extract sources and dependencies, means the tool is a component rather than a monolith. If your project resolves its classes in a way Composer does not model, you can feed the tool what it needs instead of reshaping the project.

## Three ways to install it, and the one the project recommends

Installation is a Composer requirement as a development dependency:

```bash
composer require --dev roave/backward-compatibility-check
```

There is also a Docker route, which matters more than it sounds because the tool needs a PHP runtime and the project that implements it is version specific:

```bash
docker run --rm -v `pwd`:/app nyholm/roave-bc-check
```

The volume mount is the important part. The container needs to see your repository, and the working directory is mounted at /app so the tool finds the Composer files and the git metadata where it expects them.

The third route is a GitHub Action, and the README is explicit about its preference. Tobias Nyholm offers a simple GitHub action, and the documentation says to use it for most cases because it is simple to set up. The action is written as a workflow file in the repository:

```yaml
on: [push]
name: Test
jobs:
  roave-backwards-compatibility-check:
    name: Roave Backwards Compatibility Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: "Check for BC breaks"
        uses: docker://nyholm/roave-bc-check-ga
```

That is four steps, no PHP installation and no Composer install, because the action runs the check inside a container image. For a GitHub-hosted project, the cost of the guard rail is one job definition.

## The tag problem, and why fetch-depth: 0 matters

The tool detects the base version from your git tags, and the documentation is blunt about the conditions. Detecting the base version only works if you have git tags in the SemVer-compliant x.y.z format, such as 1.2.3, and since the tool relies on tags, you need to make sure tags are fetched as part of your CI pipeline. The GitHub action example in the documentation notes the use of fetch-depth: 0 for exactly that reason.

This is the single most common way to get a wrong answer from this tool, and it fails quietly. A CI checkout that fetches a shallow clone has the current commit and no history, so there is no tag to compare against, and the tool has no baseline. Depending on what it does with a missing baseline, you either get a pass that means nothing or an error that looks like a tool fault. The default checkout depth is the problem, and fetch-depth: 0 is the one line that fixes it.

The SemVer requirement is the other half. A project that tags releases as v1.2.3 or as 1.2 or as 1.2.3-beta does not have the format the tool looks for, and the base version will not be detected. That is a naming convention rather than a technical limit, but it is a convention the tool imposes on your release process, and the documentation does not offer a setting to change it.

Both of these are visible in the example workflow, which is why that example is worth copying rather than paraphrasing.

## Output formats for a changelog or a build annotation

The default output is enough for CI, because the exit status is the signal, but the tool can also produce a report.

For changelog documentation, the manual run pipes markdown into a file:

```bash
vendor/bin/roave-backward-compatibility-check --format=markdown > results.md
```

And when running in GitHub Actions, the documentation endorses the github-actions output format:

```bash
vendor/bin/roave-backward-compatibility-check --format=github-actions
```

That second one is a small but real improvement over the default. A build log that says the build failed is a bad experience, while a build log that annotates the file and line where the API changed is a good one, because the person who pushed the change sees where to look without leaving the pull request.

The full set of options is available through the help command:

```bash
vendor/bin/roave-backward-compatibility-check --help
```

The README calls the manual run useful for generating additional documentation for changelogs, which is the use case the markdown format is for. A library that publishes a changelog per release can therefore attach the detected breaks to the release notes rather than only failing a build that nobody reads.

## Configuration lives in one XML file in the working directory

Configuration is read from a file named .roave-backward-compatibility-check.xml in the current working directory, when it exists, and it is expected to be an XML file that follows the project's own schema, which lives in the Resources directory of the package.

The example shows the shape:

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<roave-bc-check
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/roave/backward-compatibility-check/Resources/schema.xsd">
    <baseline>
        <ignored-regex>#\[BC\] CHANGED: The parameter \$a of TestArtifact\\TheClass\#method\(\)#</ignored-regex>
        <ignored-regex>#\[BC\] CHANGED: The parameter \$b of TestArtifact\\TheClass\#method2\(\)#</ignored-regex>
    </baseline>
</roave-bc-check>
```

The baseline element holds ignored-regex entries, and each one is a pattern matched against the messages the tool produces, which is how you silence a change you have decided is acceptable. The example ignores two specific parameter changes on a test class, and the escaped characters show what the patterns have to cope with, since the messages contain regular expression metacharacters of their own.

This is the practical part of using the tool. A comparison of two API versions will find changes that are breaking in form and not in practice, a renamed parameter that you control, a protected method that was never public, a class that was always internal. The configuration file is where those decisions are recorded, and the advantage of recording them is that the decision is reviewed in the repository rather than made silently in someone's head each time the build goes red.

## A version line that tracks PHP, and the tooling around it

The releases follow the PHP major version, which is how a tool like this has to be versioned. The recent tags are 8.23.0 published on 2026-09-30, 8.22.0 on 2026-09-16 and 8.21.0 on 2026-05-15, and the default branch of the repository is 8.24.x, so the development branch is the next version in the series.

That is a very active release cadence, three releases in about four and a half months in the sample above, and it matters for a tool that runs in CI. A tool that flags a new BC break in a pull request has to be updated when the reference implementation of the comparison changes, and a version line that tracks PHP means the tool is upgraded on the same schedule as the language it analyses.

The repository is not archived and the last push was on 2026-10-17, so the work is current.

The rest of the tree shows how the project tests itself, and it is a more interesting list than a typical tool's. There is infection.json.dist, which is the configuration for Infection, the mutation testing framework, so the project measures whether its own tests detect deliberate faults. There is psalm.xml for static analysis, phpcs.xml.dist for coding standards, phpunit.xml.dist for the unit tests, a .laminas-ci.json for continuous integration, and renovate.json for dependency updates. The type coverage badge comes from shepherd.dev, and the mutation testing badge from the Stryker dashboard, which is the same framework Infection is modelled on.

The two badges together make a claim worth reading carefully. A tool that decides what counts as a breaking change is only as good as its own test suite, and mutation testing is the technique that measures whether a suite is real. It is not the norm for a utility of this size to run it, and the badge suggests the maintainers treat the correctness of the tool as the thing that matters most.

## Conclusion

Roave Backward Compatibility Check fits a maintainer of a published PHP library who wants a guard rail against accidental API breaks, since the tag detection and the non-zero exit status make it a two line CI addition rather than a review discipline. It does not fit an application that has no public API to protect, and it does not fit a repository whose sources are not covered by the autoload section or whose changes are not committed, because the tool reads the project through Composer and Git. Before wiring it in, confirm your release tags follow the SemVer x.y.z format and that your CI fetches all tags, since a shallow clone makes the baseline silently wrong, and then decide which of the false positives you intend to ignore in configuration before the first failing run.

## FAQ

### How do I use roave/backward-compatibility-check in CI?

Add it with composer require --dev roave/backward-compatibility-check and run vendor/bin/roave-backward-compatibility-check. The tool detects the last minor version tagged, compares the API against the current HEAD, and returns a non-zero status when it finds a BC break, which fails the build on most CI systems.

### What does the tool need from my project to work?

The project must use git, must use composer.json to define its dependencies, all source paths must be covered by an autoload section in composer.json, and changes must be committed to git to be covered, although you can implement your own logic to extract sources and dependencies.

### Why does my CI need fetch-depth 0?

Because the tool relies on git tags, and the documentation says you need to make sure tags are fetched as part of your CI pipeline, noting the use of fetch-depth: 0 in a GitHub action. It also needs tags in the SemVer compliant x.y.z format for the base version detection to work.

### How do I ignore a BC break that I consider acceptable?

Put an .roave-backward-compatibility-check.xml file in the current working directory, following the project's schema, and add ignored-regex entries under the baseline element matching the messages you want to silence.

### Which output formats does the tool support?

The default output is enough for CI because the exit status carries the result, the markdown format is used to generate additional documentation for changelogs with --format=markdown, and the github-actions format is endorsed when running in GitHub Actions with --format=github-actions.

## Sources

- [Issues](https://github.com/Roave/BackwardCompatibilityCheck/issues)
- [License: MIT](https://github.com/Roave/BackwardCompatibilityCheck/blob/8.24.x/LICENSE)
- [README](https://github.com/Roave/BackwardCompatibilityCheck/blob/8.24.x/README.md)
- [Releases](https://github.com/Roave/BackwardCompatibilityCheck/releases)
- [Roave/BackwardCompatibilityCheck on GitHub](https://github.com/Roave/BackwardCompatibilityCheck)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/roave-backwardcompatibilitycheck
