Repository navigation
Vendored Gradle: running vendor from a subproject of a multi-project build exits 0 but wires a nested settings.gradle, so the real build stays unpatched and cd <subproject> && gradle breaks #428
Description
[agent] Found by the scheduled Gradle bug-hunt routine (ledger #319).
Summary
In a Gradle multi-project build, running socket-patch vendor (or anything else that vendors, such as scan --mode vendored / get --mode vendored) from a subproject directory is treated as if that subproject were a standalone Gradle build:
- It reports
success/applied: 1and exits 0. - It creates
<sub>/settings.gradlewith theapply from: '.socket/gradle/socket-patch.settings.gradle'line and<sub>/.socket/…. - The real build's root
settings.gradleis left untouched.
The result:
- Building from the root (
gradle :app:build, the normal invocation and what CI runs) still resolves the unpatchedcommons-text-1.10.0.jarfrom~/.gradle/caches/modules-2, with exit 0. - Building from inside the subproject (
cd app && gradle build) now fails. The new nestedsettings.gradlemakesappits own root build, so it loses the root build'sallprojects { repositories { … } }:Could not resolve all files for configuration ':runtimeClasspath'. socket-patch vex, run in the subproject, attestsnot_affected/ "Patched via Socket patch … (vendored)" for a build that doesn't use the patch.
Maven already refuses the equivalent situation: a discovered ancestor reactor gives vendor_jvm_shape_unsupported with reason: not_build_root: run vendor from reactor root …. Gradle has no such check.
Impact
This silently produces a false "patched" state, including a false VEX attestation, and breaks the subproject's standalone gradle invocation. Running a tool from the module you're working in is common in monorepos.
Repro
I used the e2e_vendor_jvm_build patch-service fixture (prebuilt_common::Server) and a staged .socket/manifest.json + blob for pkg:maven/org.apache.commons/commons-text@1.10.0, exactly as gradle_multi_project_vendor_locked_offline_tamper_and_byte_exact_revert does, with the manifest staged under app/.socket/.
proj/settings.gradle rootProject.name='s9'
include 'app'
proj/build.gradle allprojects { repositories { mavenCentral() } }
proj/app/build.gradle plugins { id 'java' }
dependencies { implementation 'org.apache.commons:commons-text:1.10.0' }
tasks.register('printCp') { doLast { configurations.runtimeClasspath.files.each { println 'CP ' + it } } }
cd proj/app && socket-patch vendor --json --offline --cwd . # exit 0, "applied": 1
git -C proj status # new: app/settings.gradle, app/.socket/** ; proj/settings.gradle unchanged
cd proj && gradle -q :app:printCp
# CP ~/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-text/1.10.0/<sha1>/commons-text-1.10.0.jar <- unpatched (no marker in META-INF/NOTICE.txt)
cd proj/app && gradle -q printCp
# FAILURE: Could not resolve all files for configuration ':runtimeClasspath'. > Could not resolve org.apache.commons:commons-text:1.10.0.
cd proj/app && socket-patch vex --offline --product pkg:maven/x/y@1
# "status": "not_affected", "impact_statement": "Patched via Socket patch 1d3c1fd2-… (vendored)"
Control: the same tree vendored from proj/ wires the root settings.gradle, and :app:printCp resolves .socket/vendor/gradle/…/commons-text-1.10.0.jar with the marker. That passes on Gradle 6.9.4, 7.6.6 and 9.8.0 (ubuntu probe) and on 8.14.3 locally.
Expected vs actual
- Expected:
docs/design/maven-vendoring.mdsays Gradle "multi-project builds" are supported. For Maven it says "Run vendoring from the reactor root. A discovered ancestor reactor producesnot_build_rootinstead of allowing a partial submodule edit." Gradle multi-project builds should be guarded the same way. Either refuse withnot_build_rootwhen an ancestorsettings.gradle(.kts)includes this directory, or wire the real root. A vendoredsuccessshould mean the committed checkout actually builds against the patched artifact. - Actual:
success, exit 0, a nestedsettings.gradleis created, the root build is unpatched, the subproject's standalone build is broken, and VEX attestsnot_affected.
OS × Gradle
| OS | Gradle | Reproduces |
|---|---|---|
| Linux | 8.14.3 (JDK 21) | yes (2/2) |
| Linux | 9.8.0 (JDK 21) | yes |
| macOS / Windows | any | not run. The logic is path-only (no OS-specific code), so I expect the same. |
First bad version: main 2463257 (#277). This is the first main with Gradle vendoring, and v4.0.0 refused gradle-only vendoring with vendor_gradle_unsupported.
Suspect code
crates/socket-patch-core/src/vendor/maven_repo.rs:285: the ancestor walk only checksmaven_reactor::contains_module. It has no Gradleinclude/settings.gradlecounterpart.crates/socket-patch-core/src/vendor/jvm/mod.rs:229-236:detecttreats any directory with abuild.gradle(.kts)as a Gradle build root.
Activity
mikolalysenko commentedon Oct 1, 2026
[agent] Triaged:
priority:p3(Gradle). Not a duplicate. Maven already has the check this needs:crates/socket-patch-core/src/vendor/maven_repo.rs:299returnsnot_build_rootfor an ancestor reactor. Gradle vendoring has no equivalent. The same "run from a member" shape is reported for Cargo in #417 and #338, but that code is separate. No open PR covers this.Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[agent] Claiming this issue as part of the full Gradle support campaign (agent / hosted / vendored modes, tested on Gradle 6.9/7.6/8.x/9.x on Linux, macOS and Windows). Branch: feat/gradle-support. Claim-ID: 2026-10-02T13:56:32Z-gradle21
Generated by Claude Code
Full Gradle support in agent, hosted and vendored modes (#646)
sbt, Mill and scala-cli support in agent, hosted and vendored modes (#…