fix(check): honor composer.lock when installing from package_file - #2830
Conversation
The lockfile copied into the tool staging directory was being ignored because the package file was installed with composer update, which re-resolves dependencies and rewrites the lock. Linters were installed at the latest versions matching the composer.json constraints instead of the locked versions, contradicting the documented behavior. Run composer install when the repository's composer.lock was staged and records the tool package, falling back to composer update otherwise since install would not add a package missing from the lock. Because the staged composer.json has require-dev collapsed into require, the staged lock file needs the same treatment: packages-dev entries are collapsed into packages so composer install accepts required packages locked as dev dependencies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes PHP package_file installs in qlty-check so that a staged composer.lock is actually honored, aligning runtime behavior with the documented “lock files are respected for reliability” guarantee.
Changes:
- Switches Composer package-file installs from unconditional
composer updatetocomposer installwhen a staged lockfile exists and contains the tool package, falling back toupdateotherwise. - When staging
composer.lock, collapsespackages-deventries intopackagesto match the already-stagedcomposer.jsonbehavior (whererequire-devis collapsed intorequire). - Adds unit tests covering install-vs-update selection and dev-package collapsing in the staged lockfile.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| qlty-check/src/tool/php/composer.rs | Stages lockfile with dev-package collapsing and chooses install vs update based on staged lockfile contents. |
| qlty-check/src/tool/php.rs | Minor formatting tweaks plus new tests asserting install is used when an appropriate lockfile is staged. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Coverage Impact - ubuntu-latest ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (2)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
|
Coverage Impact - macos-15 ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (2)
🤖 Increase coverage with AI coding...🚦 See full report on Qlty Cloud » 🛟 Help
|
Checking only for the tool package in the lock file was insufficient: composer install also hard-errors when any required package is missing from the lock file or locked at a version outside its composer.json constraint, which a stale repository lock file would trigger. Validating all of that up front would mean reimplementing composer's constraint resolution. Instead, attempt composer install whenever the repository's lock file was staged and fall back to composer update on any failure, restoring the previous behavior at the cost of one fast-failing composer run. This also makes the tool package presence check redundant, simplifying the logic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Include the composer subcommand and exit code in the failure message so the install-to-update fallback warning is actionable, and reword the lock staging debug log since the lock file is rewritten rather than copied verbatim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
noahd1
left a comment
There was a problem hiding this comment.
I find the collapsing of dev and prod packages a bit confusing but seems that this is pre-existing. I realized in looking through this too that the use case for "filtered" package files is much reduced with recent support for private packages. I think the only remaining use case is basically a performance optimization use case.
Automated PR for release 0.639.0. Review (and edit, if needed) the changelog entry below, then merge this pull request to publish the release. ## Draft release notes ### Fixed - Honor `package-lock.json` when installing node-based linters from `package_file`, so the locked versions are respected instead of npm resolving the latest versions matching the `package.json` semver ranges (#2829) - Honor `composer.lock` when installing PHP linters from `package_file`, so the locked versions are respected instead of `composer update` re-resolving to the latest matching versions (#2830) Co-authored-by: qlty-releases[bot] <181762136+qlty-releases[bot]@users.noreply.github.com>

Summary
PHP counterpart to #2829:
composer.lockwas not honored when installing PHP linters withpackage_file, contradicting the documented behavior ("When usingpackage_file, Qlty respects the locked versions for reliability").Problem
Since #1658,
update_composer_jsoncopies the repo'scomposer.lockinto the tool staging directory (when nopackage_filtersare configured), but the package file was then installed withcomposer update --no-interaction --ignore-platform-reqs— andcomposer updatere-resolves dependencies and rewrites the lock, ignoring the locked versions entirely.Repro: a repo with
"squizlabs/php_codesniffer": "^3.8"incomposer.json, a lockfile pinning3.8.0, andpackage_file = "composer.json"inqlty.tomlinstalled phpcs 3.13.5 instead of the locked 3.8.0.Fix
When the repository's
composer.lockwas staged, attemptcomposer install(which installs exactly what the lock records). If it fails — a stale lock, a missing package, a constraint mismatch, anything composer's lock validation rejects — fall back tocomposer updatewith a warning, restoring the previous behavior at the cost of one fast-failing composer run.One wrinkle: qlty collapses
require-devintorequirein the staged composer.json, butcomposer installvalidates required prod packages against the lock's prod section — so a tool locked underpackages-dev(the common case for linters) would hard-error with "Required package ... is not present in the lock file". The staged lock therefore gets the same treatment:packages-deventries are collapsed intopackages.Testing
composer install; install failure → falls back tocomposer update;package_filtersset → lock not staged,composer updateonly; dev packages collapsed in the staged lockrequire-dev); a stale lock (constraint^3.9vs locked3.8.0) falls back to update and succeeds; without a lockfile, behavior is unchanged🤖 Generated with Claude Code