In Composer, what is the content-hash in composer.lock, which composer.json changes alter it, and what does composer install do when it no longer matches?
answer
- md5 of selected composer.json keys
- require yes, autoload and scripts no
- stale hash: warning, locked versions
- missing or unsatisfied package: error
- update --lock rewrites only the hash
basics
~20 scontent-hash is an md5 of the composer.json keys that affect resolution, such as require, repositories and config.platform. On a mismatch, composer install warns and installs the locked versions; Composer 2.10 errors if a required package is missing or unsatisfied.
solid answer
~40 s`content-hash` is an md5 over the parts of `composer.json` that can change dependency resolution: `name`, `version`, `require`, `require-dev`, `conflict`, `replace`, `provide`, `minimum-stability`, `prefer-stable`, `repositories`, `extra` and `config.platform`. Editing `autoload`, `scripts` or `description` leaves it unchanged. On `install`, Composer compares it with the current `composer.json`. If it differs, Composer 2.10 warns that the lock file is not up to date and still installs the locked versions; if a root requirement is **missing from the lock** or its locked version **no longer satisfies** the constraint, the install stops with an error, as it has since 2.5, unless `allow-missing-requirements` is set. `composer validate` treats a stale lock as an error. To refresh only the hash, for example after a merge conflict on that line alone, run `composer update --lock`.
code
bash · 3 linescomposer validate --strict # stale lock is an error
composer install --dry-run # preview without touching vendor/
composer update --lock # rewrite only the content-hashgo deeper
Know that composer.lock remembers which composer.json it was made from, and that a warning about an outdated lock means someone skipped composer update.
Explain which keys feed the hash, the difference between the stale-lock warning and the missing-requirement error, and what update --lock does.
Put composer validate --strict in CI, resolve lock merge conflicts with update --lock or by re-requiring, and never let a stale lock reach a deploy.
Set the team's lock hygiene rules, such as who may change the lock and how merges are resolved, so the lock stays a trusted record rather than a merge artefact.
## What the hash is for `composer.lock` records the result of resolving one particular `composer.json`. If `composer.json` changes afterwards, the lock may describe a different set of wishes than the ones now written down. The **`content-hash`** field lets Composer notice that cheaply, without re-running the solver. ## How Composer computes it Composer 2.10 builds the hash in `Locker::getContentHash()`. It takes only the keys that can influence which versions are chosen, sorts them, JSON-encodes them and applies **md5**: - `name`, `version` - `require`, `require-dev` - `conflict`, `replace`, `provide` - `minimum-stability`, `prefer-stable` - `repositories` - `extra` - `config.platform` (and no other part of `config`) Everything else is ignored. That is why editing `autoload`, `scripts`, `description`, `license` or most `config` settings does **not** make the lock stale, and why a change to `config.platform` does. | Edit to `composer.json` | Hash changes? | |---|---| | widen `^2.0` to `^2.0 \|\| ^3.0` in `require` | yes | | add a PSR-4 entry to `autoload` | no | | add a `post-install-cmd` script | no | | set `config.platform.php` | yes | | set `config.sort-packages` | no | ## What else the lock records The hash is one field among several. A lock written by Composer 2.10 also carries: - `packages` and `packages-dev`: every resolved package with its exact version and its `source` and `dist` references; - `aliases`, `minimum-stability`, `stability-flags`, `prefer-stable` and `prefer-lowest`: the resolution settings in force; - `platform` and `platform-dev`: the root's platform requirements, plus `platform-overrides` when `config.platform` was set; - `plugin-api-version`: the plugin API of the Composer that wrote it. None of these should be edited by hand; every one is rewritten by the next update. ## What `install` does with a mismatch When `composer install` finds a lock, it first verifies the locked set against the current platform. Then: 1. **Hash differs, requirements still met.** Composer prints `Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies.` and installs the locked versions anyway. Example: you widened a constraint; the locked version still fits it. 2. **A root requirement is missing from the lock, or its locked version does not satisfy the new constraint.** Composer lists each one ("is not present in the lock file", or "is in the lock file as ... but that does not satisfy your constraint") and **stops with an error**. This has been the behaviour since Composer 2.5; the `allow-missing-requirements` config option, added in 2.8, turns it back into a pass. The first case is still a problem to fix, not a warning to live with: the next person to run `update` will get a surprise diff. ## Detecting it before a deploy - `composer validate` checks the lock against `composer.json` and reports a stale lock as an **error**; `--no-check-lock` suppresses that, which a CI job should not do. - `composer install --dry-run` shows what an install would change without touching `vendor/`. Adding `composer validate --strict` to CI catches the "edited `composer.json`, forgot to update" commit on the pull request instead of at deploy time. ## Refreshing the hash without moving versions `composer update --lock` (equivalently `composer update nothing` or `composer update mirrors`) rewrites the `content-hash` without changing any package versions, while refreshing metadata such as mirror URLs. Composer's own merge-conflict guide suggests it for the trivial case where two branches added different packages, git merged the package lists cleanly, and only the `content-hash` line conflicts. It is **not** a fix when requirements actually changed: a hash that says "fresh" over a lock missing a required package only hides the problem until the install error above. ## Lock files in libraries A library's lock is only ever read when the library is the root project, for example in its own CI. Consumers never see it, so a stale hash in a library affects only its contributors.
- Two branches each added a different package; git merged composer.lock cleanly except for the content-hash line. What do you do?Resolve the conflict by running `composer update --lock`, which recomputes the hash from the merged `composer.json` without changing versions, then run `composer validate` and `composer install --dry-run` to confirm the merged lock actually satisfies every requirement. If the two packages had conflicting dependencies, the safer path is to take one branch's files and re-run `composer require` for the other's package.
- You edit only the autoload section of composer.json. Do you need to run composer update?No. `autoload` is not part of the content hash, so the lock stays fresh. You regenerate the autoloader instead, which is a separate Composer step.
saying these in an interview costs you the question
- The content-hash is a checksum of the downloaded vendor files
- Any edit to composer.json, including autoload, makes the lock stale
- A stale content-hash makes composer install always fail
- composer update --lock updates every package to its newest version
- Hand-editing the content-hash is a safe way to silence the warning