[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
Maven 4 (model 4.1.0) discovers subprojects on its own. A root pom.xml with <packaging>pom</packaging> and no <subprojects>/<modules> element still builds every child directory that holds a pom.xml, and children can use <parent/>. socket-patch vendor routes a project into the reactor planner only when the root pom declares <modules>/<subprojects> (vendor/jvm/mod.rs:223, maven_reactor::declares_modules). An implicit-subproject reactor therefore falls through to the legacy single-POM backend (vendor/maven_repo.rs). Its multi-module refusal (vendor_maven_multimodule_unsupported, vendor/maven_repo.rs:205) checks the same declared-modules test, so it doesn't fire either.
The result is the weak same-GAV wiring: one <repository> in the root pom pointing at .socket/vendor/maven/<uuid> with the unsuffixed 1.10.0 coordinate, and no module edits, pins or .mvn/maven.config. The run reports applied: 1, vendor --check passes, and vex attests not_affected.
Impact
- The developer's own tree builds the unpatched jar. It resolved commons-text before vendoring, so
~/.m2 is warm, and the local repository wins over the root <repository> (the documented single-POM shadow). Both modules get Central's commons-text-1.10.0.jar, yet vex exits 0 with not_affected (only a stderr warning that "the installed tree does not match its vendored artifact").
- A fresh checkout can't build offline.
mvn -o fails: "Cannot access socket-patch-vendor-… in offline mode".
- The same tree with an explicit
<subprojects> list gets the suffixed reactor wiring (1.10.0-socket.1d3c1fd2 + .mvn/maven.config tail). It resolves the patched jar warm, cold and offline. So the difference comes down to whether one element that Maven 4 itself treats as optional is present.
Repro (Maven 4.0.0-rc-7, Linux, JDK 21)
mkdir -p imp/a imp/b && cd imp
cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0" root="true">
<modelVersion>4.1.0</modelVersion>
<groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version>
<packaging>pom</packaging>
</project>
EOF
cat > a/pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0">
<modelVersion>4.1.0</modelVersion>
<parent/>
<artifactId>a</artifactId>
<dependencies><dependency>
<groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>1.10.0</version>
</dependency></dependencies>
</project>
EOF
cat > b/pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.1.0">
<modelVersion>4.1.0</modelVersion>
<parent/>
<artifactId>b</artifactId>
<dependencies><dependency>
<groupId>com.example</groupId><artifactId>a</artifactId><version>${project.version}</version>
</dependency></dependencies>
</project>
EOF
mvn package dependency:3.5.0:build-classpath -Dmdep.outputFile=target/cp.txt # Maven builds root, a, b
# stage an agent-mode commons-text 1.10.0 patch (.socket/manifest.json + blob), then:
socket-patch vendor --json --offline # applied: 1, exit 0
git diff # only pom.xml: <repository> socket-patch-vendor-<uuid>; a/b untouched, no .mvn/
socket-patch vendor --check --json # vendor_check_ok, exit 0
socket-patch vex # not_affected, exit 0
mvn package dependency:3.5.0:build-classpath -Dmdep.outputFile=target/cp.txt
grep -o '[^:]*commons-text[^:]*' a/target/cp.txt # …/commons-text/1.10.0/commons-text-1.10.0.jar (Central bytes)
The capstone-style oracle is a local, uncommitted variant of e2e_vendor_jvm_build::maven_reactor (real vendor --offline through the prebuilt fixture server, then a real mvn package + maven-dependency-plugin:3.5.0:build-classpath, with the jar's META-INF/NOTICE.txt checked for the patch marker).
| Variant (4.0.0-rc-7) |
vendor |
--check |
vex |
warm in-tree build a / b |
fresh checkout, cache purged, -o |
fresh, online |
| implicit subprojects (run 1) |
applied 1, single-POM wiring |
ok |
not_affected |
1.10.0 unpatched / unpatched |
fails (offline file repo) |
1.10.0 patched |
| implicit subprojects (run 2) |
same |
ok |
not_affected |
unpatched / unpatched |
fails |
patched |
control: same tree + <subprojects>a,b</subprojects> |
applied 1, reactor wiring |
ok |
not_affected |
1.10.0-socket.1d3c1fd2 patched / patched |
patched / patched |
— |
Expected vs actual
- Expected, per docs/design/maven-vendoring.md ("Supported reactors have an explicit root
pom.xml and <modules> or <subprojects> declarations") and the CLI_CONTRACT.md maven row ("Multi-module aggregator poms refused (vendor_maven_multimodule_unsupported)"): a reactor is either planned as a reactor (suffixed coordinates, immune to the warm cache) or refused. It's never wired as a single POM. Alternatively, treat a 4.1.0 pom-packaged root with no declared subprojects, and with child dirs holding pom.xml, as a reactor (or refuse it with a hint to list <subprojects>).
- Actual: wired as a single POM with the unsuffixed coordinate. It reports success,
--check passes and vex attests, while the developer's warm build and every mvn -o build don't use the patch.
OS × version
| OS |
Maven |
Result |
| Linux |
4.0.0-rc-7 (newest 4.x) |
fails (2×) |
| Linux |
3.6.3 – 3.9.16 |
n/a: Maven 3 has no implicit subproject discovery |
| macOS / Windows |
4.0.0-rc-7 |
untested (the routing is OS-independent: a string check on the root pom) |
Tested on main 045d7ec (latest release v4.0.0). Not bisected: the v5 reactor planner introduced the declares_modules routing.
Suspect code
crates/socket-patch-core/src/vendor/jvm/mod.rs:223: detect routes to Shape::MavenReactor only when declares_modules sees <modules>/<subprojects>.
crates/socket-patch-core/src/vendor/maven_repo.rs:205: the single-POM backend's multi-module refusal uses the same declared-modules test.
[agent] Found by the scheduled Maven bug-hunt routine (ledger #318).
Summary
Maven 4 (model 4.1.0) discovers subprojects on its own. A root
pom.xmlwith<packaging>pom</packaging>and no<subprojects>/<modules>element still builds every child directory that holds apom.xml, and children can use<parent/>.socket-patch vendorroutes a project into the reactor planner only when the root pom declares<modules>/<subprojects>(vendor/jvm/mod.rs:223,maven_reactor::declares_modules). An implicit-subproject reactor therefore falls through to the legacy single-POM backend (vendor/maven_repo.rs). Its multi-module refusal (vendor_maven_multimodule_unsupported,vendor/maven_repo.rs:205) checks the same declared-modules test, so it doesn't fire either.The result is the weak same-GAV wiring: one
<repository>in the root pom pointing at.socket/vendor/maven/<uuid>with the unsuffixed1.10.0coordinate, and no module edits, pins or.mvn/maven.config. The run reportsapplied: 1,vendor --checkpasses, andvexattestsnot_affected.Impact
~/.m2is warm, and the local repository wins over the root<repository>(the documented single-POM shadow). Both modules get Central'scommons-text-1.10.0.jar, yetvexexits 0 withnot_affected(only a stderr warning that "the installed tree does not match its vendored artifact").mvn -ofails: "Cannot access socket-patch-vendor-… in offline mode".<subprojects>list gets the suffixed reactor wiring (1.10.0-socket.1d3c1fd2+.mvn/maven.configtail). It resolves the patched jar warm, cold and offline. So the difference comes down to whether one element that Maven 4 itself treats as optional is present.Repro (Maven 4.0.0-rc-7, Linux, JDK 21)
The capstone-style oracle is a local, uncommitted variant of
e2e_vendor_jvm_build::maven_reactor(realvendor --offlinethrough the prebuilt fixture server, then a realmvn package+maven-dependency-plugin:3.5.0:build-classpath, with the jar'sMETA-INF/NOTICE.txtchecked for the patch marker).--checkvex-o<subprojects>a,b</subprojects>1.10.0-socket.1d3c1fd2patched / patchedExpected vs actual
pom.xmland<modules>or<subprojects>declarations") and the CLI_CONTRACT.md maven row ("Multi-module aggregator poms refused (vendor_maven_multimodule_unsupported)"): a reactor is either planned as a reactor (suffixed coordinates, immune to the warm cache) or refused. It's never wired as a single POM. Alternatively, treat a 4.1.0pom-packaged root with no declared subprojects, and with child dirs holdingpom.xml, as a reactor (or refuse it with a hint to list<subprojects>).--checkpasses andvexattests, while the developer's warm build and everymvn -obuild don't use the patch.OS × version
Tested on main
045d7ec(latest release v4.0.0). Not bisected: the v5 reactor planner introduced thedeclares_modulesrouting.Suspect code
crates/socket-patch-core/src/vendor/jvm/mod.rs:223:detectroutes toShape::MavenReactoronly whendeclares_modulessees<modules>/<subprojects>.crates/socket-patch-core/src/vendor/maven_repo.rs:205: the single-POM backend's multi-module refusal uses the same declared-modules test.