Repository navigation
Fix Composer 1 packages moved by composer/installers (#463) - #1723
Conversation
Assisted-by: Claude Code:claude-opus-5-5
On Composer 1, installed.json has no install-path, so the crawler
looked for every package under vendor/<ns>/<name>. Packages that
composer/installers puts elsewhere (WordPress plugins, Drupal
modules, any extra.installer-paths target) were never found: agent
scan skipped them as "not installed" and exited 0, apply reported
them missing, and hosted vex attested not_affected from the lock pin
without checking the unpatched copy actually installed.
When composer/installers is installed and an entry has no
install-path, work out its directory the way the plugin does: the
first root installer-paths entry whose rules name the package, its
type: or its vendor:, with {$name}/{$vendor}/{$type} substituted
(the package's installer-name replaces {$name}), else the built-in
WordPress and Drupal locations. That directory is preferred over a
leftover vendor/ copy, and must stay inside the project like an
install-path. scan, apply and vex share the lookup.
Fixes #463
Assisted-by: Claude Code:claude-opus-5-5
End-to-end regression for #463: a Composer 1 project whose WordPress plugin lives under extra.installer-paths must be counted by scan and sent to the patch lookup. Fails on main (1 package found, the plugin is missed) and passes with the crawler fix. Assisted-by: Claude Code:claude-opus-5-5
The first cut relocated any <framework>-<kind> type and filled
{$name} with the raw package name. composer/installers only moves
types its installers list, and many frameworks rewrite {$name}
(CakePHP camel-cases it), so a guessed directory could be wrong and
was preferred over the real vendor/ copy.
Relocate only the WordPress and Drupal types composer/installers
handles (names kept as is), using DrupalInstaller's real
drupal-drupal-multisite key. Honour extra.installer-disable, require
an installer-name to be one safe path segment, and skip reading
composer.json when every entry already records an install-path
(Composer 2).
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Burn-down agent: labeled Ready for review at
Generated by Claude Code |
|
[agent] Correction to the review pointer above: the composer/installers path inference ( Generated by Claude Code |
Final review briefWhat it does: Composer 1 doesn't record Risk: low. The new path only runs for entries with no Look here:
Verified: I read the full diff and checked it against composer/installers' Changes I made: none. Open questions: none. Auto-merge is armed: approving sends this straight to the merge queue. Generated by Claude Code |
composer/installers turns every installer off when installer-disable is "*", or is a list holding true, "all" or "*". This parser treated "*" as a framework name and dropped true inside a list, so it still guessed a WordPress or Drupal directory for a package Composer left in vendor/, and could patch a stray copy there instead. Framework names now also match case-sensitively, as upstream does. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a841498. Configure here.
|
[final reviewer] Disabled auto-merge. This PR was armed only on the strength of the final-review brief, and the head has gained non-merge commits since that brief (review fixes pushed today). I'll write a fresh brief once those pushes settle and CI and Bugbot are green on the new head, then re-arm. The Generated by Claude Code |
|
Burn-down agent: Ready for review at
Generated by Claude Code |
Final review briefWhat it does: On Composer 1 ( Risk: low to medium.
Look here
Verified
Changes I made: none. Open questions (none blocking; these only cause missed packages, never wrong ones)
Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #463
Summary
On Composer 1, socket-patch could not find packages that composer/installers installs outside
vendor/, such as WordPress plugins and Drupal modules. Agentscanskipped them as "not installed" and exited 0, andget --mode agentreported them missing. Hostedvexattestednot_affectedfrom the lock pin without checking the unpatched copy that is actually installed. Those packages are now found where composer/installers put them.Root cause
resolve_package_dir(crates/socket-patch-core/src/crawlers/composer_crawler.rs) trusts installed.json'sinstall-path. When an entry has none, it always usesvendor/<ns>/<name>. Composer 2 recordsinstall-pathfor every package, but Composer 1 never does, so a package composer/installers moved was invisible.crawl_all(scan, vex) andfind_by_purls(apply) both resolve through this function.Fix
For an entry with no
install-path, when composer/installers is in installed.json and not switched off by the rootextra.installer-disable, the crawler works out the directory the way the plugin'sBaseInstaller::getInstallPathdoes:extra.installer-pathsentry (in file order) whose rules name the package, itstype:or itsvendor:;{$name},{$vendor}and{$type}are substituted, and the package'sextra.installer-namereplaces{$name}. That directory is tried beforevendor/<ns>/<name>(it is the copy the app loads) and must stay inside the project, like aninstall-path.Scope decisions, from
/code-review highon the first cut:{$name}unchanged. Frameworks like CakePHP rewrite the name, so guessing their directory could point the patch at the wrong place. Those packages stay unresolved exactly as onmain.wordpress-coreand other types the plugin doesn't handle stay invendor/. Drupal's multisite type uses its realdrupal-drupal-multisitekey.installer-namemust be a single safe path segment, because installed.json is untrusted.install-path, so Composer 2 trees pay nothing.Known limits (not regressions, also missed on
main): a vendor dir moved outside the project (COMPOSER_VENDOR_DIR=/opt/...), where the root composer.json can't be located from the vendor path; composer/installers installed only globally in$COMPOSER_HOME; composer-installers-extenderinstaller-types. A possible follow-up is to read the real directory from Composer 1'svendor/composer/autoload_*.phpmaps.Tests
Per-issue checklist:
type:wordpress-plugin→wp-content/plugins/{$name}/):crawler_composer_e2e::composer1_installer_paths_type_rule_finds_relocated_package, coveringcrawl_allandfind_by_purls;e2e_composer::scan_discovers_composer1_installer_paths_package.main(the CLI one reports 1 package instead of 2, with the plugin missed) and pass here.vendor:and first-match rules, bare-string rules,installer-name, and the WordPress/Drupal default locations;vendor/copy;library,wordpress-coreandcakephp-plugintypes, andinstaller-disable(true, a list,"all") all stay invendor/;installer-nameare rejected.main, and for the 2 review-fix tests against the previous commit.Commands run locally:
cargo fmt --all -- --check: ok.cargo clippy --workspace --all-features -- -D warnings: ok.cargo test -p socket-patch-core --all-features --no-fail-fast: every suite passes except 4 lib tests (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouched,pypi_requirements::wire_failure_rolls_back_already_written_files). Those depend on chmod denials, which root ignores, and the agent container runs as root. This PR doesn't touch those files.cargo test -p socket-patch-cli --all-features --liband--test e2e_composer e2e_composer_vendor_dir_hints e2e_composer_version_identity e2e_vendor_composer_crlf: ok.cargo test --workspacedidn't fit in the agent container's disk allowance, so CI covers the rest.https://claude.ai/code/session_01MG72xjJojwpExvkktfHyTs
Note
Medium Risk
Changes where scan/apply resolve on-disk package paths for patching, including path inference from untrusted
composer.json/installed.json, though containment and installer-name guards limit write targets.Overview
Fixes #463: Composer 1 projects with composer/installers no longer miss packages installed outside
vendor/(e.g. WordPress plugins underwp-content/plugins/) wheninstalled.jsonhas noinstall-path.The Composer crawler adds
InstallerLayoutto mirror composer/installers when any entry lacksinstall-pathandcomposer/installersis present: it reads rootextra.installer-paths/installer-disable, applies built-in WordPress/Drupal default locations, renders{$name}/{$vendor}/{$type}(andextra.installer-name), and prefers the relocated directory overvendor/<ns>/<name>when both exist.crawl_allandfind_by_purlsnow resolve via ordered path candidates andfirst_existing_dirs;contain_in_projectand single-segmentinstaller-namechecks keep untrusted metadata from escaping the project. Composer 2 trees that already recordinstall-pathskip readingcomposer.json.Coverage includes core e2e cases (rules, defaults, disable, stale vendor copy, path rejection) and a CLI scan e2e asserting relocated packages reach patch lookup.
Reviewed by Cursor Bugbot for commit a841498. Configure here.
Generated by Claude Code