composer.lock not committed by library

Checks that a library does not ship a composer.lock file in its released package.
category
maintenance
weight
2

How Plumb checks this

What is inspected, and how the result is decided.

A composer.lock file records the exact dependency versions one project installed. Composer ignores a lockfile that sits inside an installed dependency, so a library shipping one does not affect anyone's install. The harm is indirect: security scanners and audit tools that walk the vendor/ directory read any lockfile they find and report the versions pinned inside it, which are not the versions the consuming project actually uses. The result is false alarms for every downstream user.

Plumb inspects the released archive, which is exactly what composer require downloads, rather than the repository. It does not matter how the file was kept out of the archive: export-ignore in .gitattributes, archive.exclude in composer.json, or never committing the file all work.

The check passes when composer.lock is absent from the released archive and fails when it is present. It is not applicable when the package declares type: project in its composer.json, because applications such as a Laravel or Symfony skeleton are supposed to commit a lockfile, and when there is no stable release to inspect. This is a low-weight hygiene signal.

Doing it well

Why this matters

composer.lock records the exact versions of every dependency that one particular project installed. For an application, committing it is right: everyone who deploys the application gets the same versions. For a library, it is a different story. When someone installs your library, Composer ignores your lockfile completely and resolves versions against their own project. Shipping it changes nothing about how your library installs.

What it does change is what security scanners see. Tools that walk a project's vendor/ directory find your composer.lock, read the versions pinned inside it, and report vulnerabilities in packages the consuming project may not even have installed at those versions. Every downstream user gets false alarms that trace back to your package.

What good looks like

The archive that Composer downloads for your library does not contain composer.lock. Whether you commit the file to the repository is a separate choice; what matters is that it stays out of the released archive.

How to do it

You have two options, and you can use both.

Option 1: keep the lockfile out of the archive

Keep composer.lock in the repository for reproducible CI, but exclude it from the release archive with .gitattributes:

/composer.lock export-ignore

Git honours export-ignore when building archives, and Packagist and GitHub build release archives with Git, so the file disappears from what users download. Commit the .gitattributes change and tag a new release.

Alternatively, list it under archive.exclude in composer.json:

{
  "archive": {
    "exclude": ["/composer.lock"]
  }
}

Option 2: do not commit it at all

Add composer.lock to .gitignore and remove it from the repository:

echo "/composer.lock" >> .gitignore
git rm --cached composer.lock
git commit -m "Stop committing composer.lock"

Your CI then resolves fresh dependencies on every run, which has a side benefit: you find out quickly when a new dependency release breaks your library.

Tag a release

The released archive is what counts. After either change, tag a new version so the fix reaches users.

Things to watch for

  • Fixing the repository but not releasing. The old archive still contains the lockfile until a new tag is published.
  • Applications and skeletons. If your composer.json declares "type": "project", committing and shipping a lockfile is correct. This guidance is for libraries, plugins, and bundles.
  • Monorepos. A lockfile at the monorepo root is fine; what matters is the archive of each published package.
  • Losing reproducible CI. If you stop committing the lockfile, consider running CI with both --prefer-lowest and the latest versions so you test the whole range you declare.

Further reading