[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
MavenCrawler::m2_repo_path() decides which Maven local repository agent mode and -g work on. It checks, in order, $MAVEN_REPO_LOCAL, then $M2_HOME/repository, then ~/.m2/repository. That doesn't match Maven:
M2_HOME is the Maven installation directory. No Maven version keeps or looks for a local repository under it. Many machines still export it (Jenkins tool installers, Windows setup guides, older Docker images). With it set, socket-patch looks in /opt/apache-maven-x/repository, which doesn't exist, while Maven keeps using ~/.m2/repository.
<localRepository> in ~/.m2/settings.xml is never read. That's the standard way to move the local repository (corporate setups, CI caches, a separate disk). Maven uses the configured directory, and socket-patch looks at the empty or stale ~/.m2/repository.
In both cases the crawler finds an empty or missing directory and every command treats it as "nothing installed":
scan -g, scan -g --mode agent --yes, and project-mode scan --mode agent report scannedPackages: 0 with status: success and exit 0. Nothing is checked and nothing is patched.
rollback -g exits 0 with skipped: package_not_installed. It also removes the manifest entry (manifest.removedEntries) and garbage-collects the blob. The jar or pom in the real local repository stays patched, and since the record is gone, a later rollback -g with a correct environment can't restore it (rolledBack: 0).
apply -g does fail loudly (exit 1, skipped: 1), as the contract says, but the message calls the package not installed when it is.
Impact
scan -g misses every global Maven install on any machine with M2_HOME set or a relocated local repository. The rollback case loses data: the user is told the tree is clean (exit 0, "not installed"), the only record of the patch is deleted, and Maven goes on building with the patched bytes from the real local repository.
Repro (Linux, Maven 3.9.11, JDK 21)
You need a stub patch API that serves one agent patch for pkg:maven/org.apache.commons/commons-text@1.10.0: it appends <!-- SOCKET-PATCH-GLOBAL-MARKER --> to commons-text-1.10.0.pom, with the same shapes as the wiremock mock in tests/docker_e2e_maven.rs.
SP=target/release/socket-patch
A="--api-url http://127.0.0.1:18998 --api-token fake --org org --ecosystems maven"
export HOME=$(mktemp -d); export MAVEN_OPTS=-Duser.home=$HOME
REL=org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.pom
# a real global install in the default local repository
mvn -q org.apache.maven.plugins:maven-dependency-plugin:3.1.2:get -Dartifact=org.apache.commons:commons-text:1.10.0
cd $(mktemp -d)
$SP scan -g --mode agent --yes --json $A # applied: 1; the global pom now has the marker
# Trigger 1: M2_HOME pointing at the Maven install (Maven itself still uses ~/.m2/repository)
export M2_HOME=/opt/apache-maven-3.9.11
mvn -X -o validate | grep 'Using local repository' # -> $HOME/.m2/repository
$SP scan -g --json $A | grep scannedPackages # -> 0
$SP rollback -g --yes --json $A # rc=0, skipped package_not_installed,
# removedEntries=[...commons-text@1.10.0], removedBlobs=1
grep -c GLOBAL-MARKER ~/.m2/repository/$REL # -> 1 (still patched)
cat .socket/manifest.json # -> {"patches":{}}
unset M2_HOME; $SP rollback -g --yes --json $A # rolledBack: 0, still patched; nothing left to restore it from
# Trigger 2: <localRepository> in ~/.m2/settings.xml
echo '<settings><localRepository>/data/m2</localRepository></settings>' > ~/.m2/settings.xml
# (with the global install living in /data/m2) the same results: scan -g -> scannedPackages 0;
# rollback -g -> package_not_installed, the record is dropped, /data/m2 stays patched
Control: without M2_HOME and with the repository at ~/.m2/repository, the same scan -g reports scannedPackages: 244, and rollback -g restores the pom byte for byte. --global-prefix /data/m2 also finds all 244 packages.
Expected vs actual
- Expected:
-g (and agent mode in a project) uses the local repository Maven actually uses. The maintainer brief for global mode asks for that ("~/.m2/repository, settings.xml <localRepository>, MAVEN_REPO_LOCAL, -Dmaven.repo.local"), and the crawler's own doc says it returns ~/.m2/repository/. CLI_CONTRACT.md (rollback contract, "Exit rules") says "Everything that leaves the system still patched DOES flip it to partial_failure exit 1".
- Actual: it uses
$M2_HOME/repository and ignores settings.xml. The scan reports success with 0 packages, and the rollback reports success while leaving the system patched and deleting the record.
Matrix (Linux, JDK 21; each run twice)
Maven's own choice was checked with mvn -X ("Using local repository at …"). Every line uses ~/.m2/repository when M2_HOME is set, and the <localRepository> directory when settings.xml sets one.
| Maven |
M2_HOME set: scan -g / rollback -g |
settings.xml <localRepository>: scan -g / rollback -g |
| 3.6.3 |
fail (0 scanned) / fail |
fail / fail |
| 3.8.8 |
fail / fail |
fail / fail |
| 3.9.11 |
fail / fail |
fail / fail |
| 4.0.0-rc-7 |
fail / fail |
fail / fail |
The crawler doesn't depend on the Maven version, so the scan and rollback rows are the same socket-patch result on every line. macOS and Windows are untested, but the code path doesn't depend on the OS (on Windows %M2_HOME% is set even more often).
First bad version: not a v5 regression. The same m2_repo_path() (with M2_HOME, without settings.xml) is already in 4.0.0 (f6b7fb9). Tested on main 2463257.
Suspect code
crates/socket-patch-core/src/crawlers/maven_crawler.rs:709 m2_repo_path(): the M2_HOME arm, and no settings.xml lookup (~/.m2/settings.xml, then $MAVEN_HOME/conf/settings.xml / ${maven.home}, with ${user.home} / ${env.X} interpolation).
maven_crawler.rs:565 get_maven_repo_paths() returns an empty list when the directory is missing, so the misdiscovery turns into "nothing installed" with no warning.
- Rollback dropping manifest entries and blobs for
package_not_installed turns the misdiscovery into data loss. At least under -g, a not-found entry should keep its record, or warn.
The NuGet version of this bug (globalPackagesFolder ignored) is #397.
[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
MavenCrawler::m2_repo_path()decides which Maven local repository agent mode and-gwork on. It checks, in order,$MAVEN_REPO_LOCAL, then$M2_HOME/repository, then~/.m2/repository. That doesn't match Maven:M2_HOMEis the Maven installation directory. No Maven version keeps or looks for a local repository under it. Many machines still export it (Jenkins tool installers, Windows setup guides, older Docker images). With it set, socket-patch looks in/opt/apache-maven-x/repository, which doesn't exist, while Maven keeps using~/.m2/repository.<localRepository>in~/.m2/settings.xmlis never read. That's the standard way to move the local repository (corporate setups, CI caches, a separate disk). Maven uses the configured directory, and socket-patch looks at the empty or stale~/.m2/repository.In both cases the crawler finds an empty or missing directory and every command treats it as "nothing installed":
scan -g,scan -g --mode agent --yes, and project-modescan --mode agentreportscannedPackages: 0withstatus: successand exit 0. Nothing is checked and nothing is patched.rollback -gexits 0 withskipped: package_not_installed. It also removes the manifest entry (manifest.removedEntries) and garbage-collects the blob. The jar or pom in the real local repository stays patched, and since the record is gone, a laterrollback -gwith a correct environment can't restore it (rolledBack: 0).apply -gdoes fail loudly (exit 1,skipped: 1), as the contract says, but the message calls the package not installed when it is.Impact
scan -gmisses every global Maven install on any machine withM2_HOMEset or a relocated local repository. The rollback case loses data: the user is told the tree is clean (exit 0, "not installed"), the only record of the patch is deleted, and Maven goes on building with the patched bytes from the real local repository.Repro (Linux, Maven 3.9.11, JDK 21)
You need a stub patch API that serves one agent patch for
pkg:maven/org.apache.commons/commons-text@1.10.0: it appends<!-- SOCKET-PATCH-GLOBAL-MARKER -->tocommons-text-1.10.0.pom, with the same shapes as the wiremock mock intests/docker_e2e_maven.rs.Control: without
M2_HOMEand with the repository at~/.m2/repository, the samescan -greportsscannedPackages: 244, androllback -grestores the pom byte for byte.--global-prefix /data/m2also finds all 244 packages.Expected vs actual
-g(and agent mode in a project) uses the local repository Maven actually uses. The maintainer brief for global mode asks for that ("~/.m2/repository,settings.xml<localRepository>,MAVEN_REPO_LOCAL,-Dmaven.repo.local"), and the crawler's own doc says it returns~/.m2/repository/. CLI_CONTRACT.md (rollback contract, "Exit rules") says "Everything that leaves the system still patched DOES flip it topartial_failureexit 1".$M2_HOME/repositoryand ignoressettings.xml. The scan reports success with 0 packages, and the rollback reports success while leaving the system patched and deleting the record.Matrix (Linux, JDK 21; each run twice)
Maven's own choice was checked with
mvn -X("Using local repository at …"). Every line uses~/.m2/repositorywhenM2_HOMEis set, and the<localRepository>directory when settings.xml sets one.M2_HOMEset: scan -g / rollback -g<localRepository>: scan -g / rollback -gThe crawler doesn't depend on the Maven version, so the scan and rollback rows are the same socket-patch result on every line. macOS and Windows are untested, but the code path doesn't depend on the OS (on Windows
%M2_HOME%is set even more often).First bad version: not a v5 regression. The same
m2_repo_path()(withM2_HOME, withoutsettings.xml) is already in 4.0.0 (f6b7fb9). Tested on main2463257.Suspect code
crates/socket-patch-core/src/crawlers/maven_crawler.rs:709m2_repo_path(): theM2_HOMEarm, and nosettings.xmllookup (~/.m2/settings.xml, then$MAVEN_HOME/conf/settings.xml/${maven.home}, with${user.home}/${env.X}interpolation).maven_crawler.rs:565get_maven_repo_paths()returns an empty list when the directory is missing, so the misdiscovery turns into "nothing installed" with no warning.package_not_installedturns the misdiscovery into data loss. At least under-g, a not-found entry should keep its record, or warn.The NuGet version of this bug (
globalPackagesFolderignored) is #397.