Repository navigation
Full Gradle support in agent, hosted and vendored modes - #646
Merged
Merged
Conversation
Introduce crawlers/jvm_cache: one list of JVM project markers, a layout-tagged cache root (Maven2 / GradleModules2 / Coursier / Ivy) that MavenCrawler crawls and resolves PURLs through, and a per-build-tool project_dependency_set provider list. Behavior is unchanged: only the Maven2 root is populated and no provider is registered yet. Gradle and sbt support each plug into this seam from their own modules. Also counts build.gradle.kts and settings.gradle(.kts) as manifest markers in scan policy, which previously listed only build.gradle. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Gradle detect/run/skip helpers, the classpath and lockfile readers, the project writer and the Windows verbatim-path strip lived inside e2e_vendor_jvm_build.rs, so every new Gradle suite would have had to copy them. They move to tests/gradle_build_common/ unchanged in behaviour, and the module gains what the agent/hosted/vendored suites need: the Gradle major/minor and JDK banner, Isolated Projects runs, per-DSL project writers, a configuration-cache-safe printRuntimeClasspath task plus an assertion on the bytes Gradle actually consumed, a test-only mirror init script for the fake origins, an autocrlf clone and per-cell probe reports. The launcher scrub now also drops GRADLE_RO_DEP_CACHE and GRADLE_HOME. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Gradle hosted suites drive the same Socket API and suffixed maven2 repository as the real-Maven hosted capstone. The wiremock Server, the API mounts and the suffixed-pom rewrite move to tests/hosted_maven_common/ behind a Hosted descriptor of the patched GAV and grant, so they can be reused for other coordinates. e2e_redirect_maven_build keeps its constants and thin wrappers and behaves exactly as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Once the crawlers learn Gradle's user home they resolve it from GRADLE_OPTS / JAVA_OPTS (-Dgradle.user.home), GRADLE_USER_HOME and ~/.gradle, plus the read-only GRADLE_RO_DEP_CACHE, and m2 from ~/.m2. Inherited as-is, a developer's warm caches would leak into every test that does not pin them. The common and prebuilt harnesses now scrub those variables by default and pin HOME / USERPROFILE to an empty stand-in (carrying version-manager roots over); a test passes a cache explicitly when it wants one, and prebuilt_common's fixture server serves explicit GRADLE_USER_HOME / GRADLE_RO_DEP_CACHE trees as maven2 repositories (with a slot for sbt's COURSIER_CACHE). Install detection learns the files-2.1/<sha1>/ layout (the jar and the pom live in different hash dirs), and fabricate_files21 lays out a Gradle cache under the real sha1 names, padded or with leading zeros dropped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gradle 9 prints `Launcher JVM:` / `Daemon JVM:` instead of `JVM:`, so the JDK the harness logged and probed was empty on 9.x. The new SOCKET_PATCH_GRADLE_E2E_ARGS knob appends arguments to every Gradle run, which is how the compatibility grid's configuration-cache and Isolated Projects cells reach every suite without per-test plumbing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Gradle suites need artifacts real Central cannot give them on demand: a victim at two versions with Gradle module metadata and classifier jars, a transitive range consumer, a parent pom, a BOM, a platform .module that requires the victim, a buildscript-classpath library whose class prints a marker from build logic, artifact-level maven-metadata.xml, checksum sidecars, PGP signatures, and a jar whose sha1 starts with 0 (Gradle drops that zero from the files-2.1 hash dir). tests/jvm_fixture_repo/ generates all of it byte-for-byte reproducibly (stored zip entries with fixed timestamps and permissions, hand-assembled Java 8 class files, fixed-order JSON/XML, a tabulated MD5) and serves it from wiremock as FakeCentral, with overlays and a patched-jar route for the member-keyed swap. Only the signatures of a committed THROWAWAY key, the key itself and SHA256SUMS are committed; the stability self-test regenerates the repository on every OS and compares it with SHA256SUMS, and SOCKET_PATCH_JVM_FIXTURES_REGENERATE=1 re-signs it reproducibly. gradle_multi_project_fake_central_mirror_smoke_both_dsls resolves the victim through the test-only mirror init script in both DSLs under FAIL_ON_PROJECT_REPOS, through a pom range and from the settings buildscript classpath, and records the hash-dir naming in a probe report. Locally Gradle 6.9.4, 7.6.6, 8.14.3 and 9.8.0 all name the dir with the leading zero dropped. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Gradle campaign lands its suites package by package, so CI needs the
rows before the tests exist, without letting an empty leg pass forever.
ci.yml: JVM legs carry `jvm_tool` (gradle | maven | sbt). One step picks
the JDK from the runner image (JAVA_HOME_<N>_X64 / _arm64), falling back
to setup-java, and decides whether Maven is needed: Maven legs, and Gradle
legs whose filter selects a Maven-seeded test (gradle_vendor_*,
multi-project); agent / hosted Gradle legs run without it. The PR tier
is the lean table: ubuntu x {6.9.4/11, 7.6.6/17, 8.14.3/21, 9.8.0/21} x
{agent + hosted, vendor + multi-project}, plus the existing windows
8.14.3 multi-project leg. A row's `suite` may list several binaries;
`allow_empty` skips suites that have not landed and tolerates zero tests,
and a Gradle leg without it that runs nothing fails. Probe reports are
uploaded.
gradle-compatibility.yml runs the full grid (3 OSes x 4 lines x 3 modes,
fail-fast off, 60 min) plus JDK-ceiling, configuration-cache, Isolated
Projects (recording only) and real-Central rows, path-filtered on PRs,
nightly and on dispatch. It compiles its own binaries once per OS and
documents the JDK ceilings per Gradle line; 9.8.0 is still current.
ci-e2e-bundle.py learns multi-suite rows, `--suites` and a per-suite
prefix guard: every #[ignore] test of a Gradle suite must start with
gradle_agent_ / gradle_hosted_ / gradle_vendor_ / gradle_multi_project,
the prefixes the rows filter on, or the bundle (and `--check`) fails.
test_ci_gradle_prefixes.py covers the guard (including a stray name) and
forces `allow_empty` off once every suite of a row has landed;
test_ci_e2e_tiers.py pins the PR table, the jvm_tool steps and the grid
expansion.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two format!-of-a-constant pom heads and a cloned single-element slice in the files-2.1 self-test; behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A developer's global commit signing or hooks would break the fixture commit, and a global autocrlf would change what the clone checks out. The helper now runs git against an empty global config, and a self-test pins the result: LF as committed, CRLF in the clone, -text files untouched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gradle support in agent, hosted and vendored mode needs one shared,
filesystem-free model of a Gradle build so every mode reads scripts,
versions and locks the same way and can be tested in memory on every OS.
This starts crate::gradle with the contract the later packages code
against: TextReadFn / ListFn / Env / Os.
- dsl: a comment- and string-aware Groovy/Kotlin tokenizer (copied from
the vendored planner's lexer and extended: Kotlin raw strings and
nested comments, `${}` templates with nested strings, decoded escapes,
BOM handling, strict UTF-8 decode) plus call-site parsing for both
parenthesised and Groovy command-expression calls.
- eol: CRLF sniffing, re-spelling and line-ending-blind comparison for
files a core.autocrlf clone checks out with CRLF.
- selector: Gradle's version ordering and selector scheme. Checked
against real Gradle 6.9.4, 7.6.6, 8.14.3 and 9.8.0, which showed two
behaviours changed in Gradle 7 (the special-qualifier set, and an
exclusive upper bound also rejecting qualified versions of the bound,
so `[1.9,1.10.0)` admits `1.10.0-socket.<hex>` only on 6.x), so the
comparator and admits take the Gradle major. The golden tables are
exported for the hosted script's Groovy port, and
tests/gradle_selector_golden.rs asks real Gradle for every row when
SOCKET_PATCH_GRADLE_E2E_GRADLE is set.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Discovery and agent mode must find the same Gradle caches Gradle itself uses, and hosted mode must rewrite locked versions without disturbing anything else in a lock file. - home: GradleHome::resolve over an explicit Env (the process-env adapter lives with the crawler): -Dgradle.user.home from GRADLE_OPTS then JAVA_OPTS (quote-aware per OS, last wins), a non-empty GRADLE_USER_HOME, then <home>/.gradle with USERPROFILE first on Windows; the files-2.1 cache, the read-only GRADLE_RO_DEP_CACHE copy, GRADLE_HOME, and the init-script locations (init.gradle(.kts), both init.d directories, sorted as Gradle runs them). - locks: every gradle.lockfile / buildscript- / settings- lock file and legacy gradle/dependency-locks/*.lockfile under a root (pruning build output, .gradle, node_modules, .socket and .git, eight levels deep), a parser for both formats with empty= and CRLF, and a one-entry rewrite that keeps each line's ending and configuration tail and merges into an already-locked target version. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every mode has to reason about the whole build, not only the root scripts: #461 (exclusiveContent and Android checks that only read the root build), #428 (vendoring from a subproject), #511 / #533 (range, rich, catalog and classifier declarations the root-only scan missed) and #551 (mavenLocal declared in an init script or convention plugin). ScriptGraph::collect follows, statically and with caps (8 levels of apply-from / included-build nesting, 512 files, 1 MiB each): the root settings, literal include forms with implied parents, projectDir and buildFileName overrides, each project's build script, buildSrc and literal includeBuild roots with their subprojects and precompiled convention plugins, literal apply-from targets (including rootProject.file, file(), new File(rootDir, ..) and "$rootDir/.." spellings, with a visited set), each build's libs.versions.toml and versionCatalogs files(..) catalogs, and the caller's init scripts. Anything it cannot follow (computed paths, URLs, escapes, missing, oversized or malformed files, caps) lands in `unresolved`, so callers that must fail safe can. Queries: settings_includes / project_dirs, subproject_owner for an ancestor settings file, declarations_of (string, map, Kotlin named and positional, rich version blocks, `!!`, classifier in all four forms, catalog entries with version refs), exclusive_content_filters with filter_claims_group (non-literal or uncompilable rules claim), android_or_kmp, settings_classpath_has, maven_local (Declared / NotDeclared / Undetermined), custom_lock_file and wrapper_version. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
print_cp_task's Kotlin branch read a script-level `val`, so the doLast lambda dragged the script object into the configuration cache and every --configuration-cache run failed; GRADLE_APP captured the configuration provider, which the cache serializes as a fixed file collection that `.get()` then rejects. Both now capture a task-local FileCollection. A cache reuse also skips the settings script, so the multi-project capstone only asserts the settings marker when configuration ran (configuration_reused). A new gradle_multi_project test runs the print task twice under --configuration-cache in both DSLs on Gradle >= 8.1, so the "configuration-cache safe" claim is exercised on every vendor leg. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The guard admitted every Gradle-campaign prefix in every suite, but each suite only runs under the prefixes its own rows select: a gradle_vendor_ test in the discovery suite, or a gradle_hosted_ test in the agent suite, passed --check and then ran in no row (or only on the ubuntu PR tier). GRADLE_SUITE_PREFIXES now maps each suite to its rows' prefixes, and a test pins that every admitted (suite, prefix) pair is selected by a ci.yml row and a gradle-compatibility.yml mode. Both workflows summed passed tests across a leg's suites, so one suite's tests hid another whose filter selected nothing, and the real-Central row (filter gradle_vendor_511/487) counted the always-landed e2e_vendor_jvm_build and failed on every run until WP3 lands. Each landed suite must now run a test on its own; compat rows take a `suites` override, and the real-Central row names only e2e_vendor_gradle_build. The SOCKET_PATCH_GRADLE_E2E_REAL_CENTRAL knob gains its reader, gradle_build_common::real_central(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review found several shapes where the graph gave a confident answer
(mavenLocal NotDeclared, no declarations, "not owned") for builds Gradle
actually configures differently, which would drop ~/.m2 as a root or
miss the vulnerable declaration:
- `mavenLocal { content { … } }` (the Action form, the usual way to
scope it) now counts as a declaration, not just `mavenLocal(`.
- Scripts a settings script applies are parsed as settings too, spliced
in at the `apply from` as Gradle runs them, for both the graph and
subproject_owner. Inside them `file()`, nested `apply from` and catalog
`files()` resolve against the applied script's own directory while a
bare `includeBuild 'x'` stays settings-relative (measured on Gradle
6.9.4, 7.6.6 and 9.8.0).
- include / projectDir statements are applied in source order, and an
implied child is created under its parent's directory as it stands at
the include (Gradle puts 🅰️ b at modules/a/b after relocating :a).
- Binary plugin sources (.kt/.java/.groovy) of buildSrc and of plugin
projects in included builds are read as ScriptKind::PluginSource, so a
`repositories.mavenLocal()` or plugin id in a Plugin<Project> class is
seen. Product sources of ordinary included builds are not read.
- android_or_kmp also reads catalog `[plugins]` ids, which is the only
place the id appears for `alias(libs.plugins.android.application)`.
- ScriptGraph::lockfile_paths / locks::lockfile_paths_in list only the
lock files of the build's own projects, so a hosted rewrite cannot
touch a nested sample or fixture build the checkout does not include.
locks::lockfile_paths stays as the whole-tree inventory.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Unix the JVM's user.home is the passwd entry's home, not $HOME, and Gradle derives its user home from it; in container CI jobs the two differ (HOME=/github/home, pw_dir=/root), so the CLI would scan and patch a cache Gradle never reads. GradleHome::resolve now prefers the caller's home_dir (which must be the passwd home) over $HOME on Unix. A wrapper build runs the init.d of the distribution it unpacked under <user home>/wrapper/dists, not $GRADLE_HOME's, and custom corporate distributions ship mavenLocal/mirror scripts there. init_scripts_with now includes every unpacked wrapper distribution's init.d, and init_scripts_for / wrapper_init_dirs narrow that to the build's distributionUrl. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Disassembling VersionRangeSelector / DefaultVersionSelectorScheme (the same on 6.9.4, 7.6.6 and 9.8.0) showed a range bound cannot hold whitespace, the single-value form allows whitespace only after its `[`, and both `[a]` and `[a,a]` become ExactVersionSelector (string equality). The port's lazy bound pattern accepted `[ 1.1 ]` and inner spaces, and `[a,a]` compared instead of matching exactly. New golden rows pin each case, including `[1.01]` NOT admitting `1.1`, and pass against real Gradle on all four majors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s, home, eol, selectors) Brings in the pure, I/O-light Gradle layer (DSL scanning, script graph, lockfiles, Gradle home resolution, EOL handling, version selectors) that the later work packages build on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ted fixtures, CI Gradle tiers, prefix guard) WP5a lands the shared real-Gradle test harness, deterministic fake Maven Central and hosted-API fixtures, the ci.yml jvm_tool scaffold with lean Gradle rows, and the path-filtered gradle-compatibility.yml grid that the later mode packages build on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aca8b1c routed every cache root through a per-layout match whose GradleModules2 arms were empty, so `scan --global-prefix ~/.gradle/caches/modules-2/files-2.1` found nothing (before, the plain .pom walk happened to read the poms there). Add crawlers/gradle_cache with the files-2.1 layout: a walk over exactly three literal levels (group keeps its dots, then artifact, version) and the 1-40 hex digit hash dirs below them, skipping bookkeeping and unsafe coordinates. Each version dir with `<a>-<v>.{jar,pom,module}` in some hash dir is one package whose path is the version dir; find_by_purls resolves the same dirs. hash_eq / pristine compare hash dir names as 40-digit numbers, since some Gradle releases drop the sha1's leading zeros. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Gradle package path is the version dir, but its files live one level down, each in the hash dir its sha1 names, and the same jar can sit in two hash dirs after a re-download. Every join site (apply, rollback, verify, VEX, select_installed_variants) needs the real file locations. Add gradle_cache::installed_copies, the one layout-agnostic resolver: a Gradle version dir maps each key's file name to every hash dir holding it (keys found nowhere stay on the version dir so they verify as not found); any other path is returned unchanged. installed_copies_detailed reports the missing keys apart. jvm_cache::locate_artifact lists every copy of one artifact file per cache layout, and gradle_cache::stale_derived_copies finds the instrumented and transformed copies Gradle keeps outside files-2.1, for agent mode to refuse on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Gradle build resolves from its user home's files-2.1 (and the read-only GRADLE_RO_DEP_CACHE), not ~/.m2, unless something declares mavenLocal(). Scanning only m2 missed every Gradle-cached package (#349) and reported m2 contents a Gradle-only build never uses (#551). Cache roots now come from a JvmEnv (process env by default, an explicit Env in tests): the Gradle user home resolved like Gradle does it (the gradle.user.home property, GRADLE_USER_HOME, then the passwd home on Unix) and the Maven local repository. A Gradle build, or a global scan, crawls files-2.1 and the read-only cache. m2 stays a scan root for a pom.xml, a non-Gradle cwd, a global scan, or a Gradle build where the script graph plus the init scripts that apply (user home, GRADLE_HOME, and the wrapper's own distribution, wherever distributionBase/Path unpack it) declare mavenLocal() or cannot rule it out. A custom wrapper distribution that is not unpacked yet, an unreadable init script or an unfollowable script reference keeps m2 (undetermined). PURL lookups keep m2 regardless, since its bytes still serve vendoring and apply. --global-prefix accepts a Gradle user home, caches/modules-2 or a read-only modules-2 for the files-2.1 inside them; a Maven repository named `caches` is left alone. jvm_cache gains all_local_roots for byte sourcing, gradle_cache the fs/env adapters (fs_text_read, fs_list, home_from_process_env, init-script reads) the other Gradle packages share, and locked_gavs for the lock-membership annotation. The CLI test harness now also pins GRADLE_USER_HOME to the stand-in home: with Gradle's home taken from the passwd entry, pinning HOME alone would let a developer's real ~/.gradle into every test. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scan now says when the Gradle side of discovery is not what the user may expect, on the run-level warnings[] channel: - gradle_build_ignores_m2: a Gradle-only build declares no mavenLocal(), and modules its locks or patch records name exist only in ~/.m2, which the build never resolves from and the scan leaves out (#551). - gradle_maven_local_undetermined: m2 stays a root because a script or init script could not be read literally. - gradle_user_home_differs: Gradle's home follows the passwd entry, not $HOME. Each Gradle-cached package in packages[] carries an additive inLock flag from the build's graph-scoped lock files. It only annotates: an unlocked buildscript or plugin dependency is still reported. e2e_gradle_discovery_build runs the real binary against fabricated caches everywhere, and gradle_agent_349_scan_finds_gradle_cache lets real Gradle fill a fresh user home from the fake Central, then checks the scan reports the module and that the crawled version dir expands to the hash dir whose jar Gradle consumed. On 6.9.4, 7.6.6, 8.14.3 and 9.8.0 that dir drops the jar sha1's leading zero. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
get_maven_repo_paths now lists the Maven local repository ahead of the Gradle caches. Callers that still take the first copy (agent apply's Maven arm, hosted VEX copies) then keep resolving where they always did; the Gradle copies remain reachable for the all-copies fan-out that follows. e2e_maven's Gradle-marker scan now declares mavenLocal(): a Gradle-only build without it does not read ~/.m2, so the scan no longer counts the m2 artifact for it (#551). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
stale_derived_copies counted any file named after the jar as stale and stopped silently after 200,000 entries. A copy Gradle rebuilt from the patched jar has the same name, so the warning could never clear, and a truncated walk looked exactly like "no stale copies". It now returns DerivedCopies: copies proven pristine-derived (identical bytes or the pristine sha1 in a dir or stem) are stale, other same-named files are unknown, and an entry cap or unreadable entry sets incomplete. The walk is sorted so it is deterministic. gradle-wrapper.properties is now read the way java.util.Properties reads it: ISO-8859-1, whitespace as a separator, escapes and continuations. A wrapper file that names no distribution, or cannot be read, marks mavenLocal() undetermined instead of falling back to "no wrapper", so a custom distribution's init.d cannot drop ~/.m2 silently. get_maven_repo_paths no longer returns Gradle files-2.1 roots. Its callers (apply, rollback, vendor, VEX) join file keys onto the package path. A Gradle version dir holds no files directly, so a Gradle-only GAV failed with NotFound instead of being skipped as not installed. The Gradle roots move to the new get_maven_copy_paths for callers that expand version dirs through installed_copies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The vendor baseline pre-check ran verify_file_patch on the crawled package path, and for a Gradle version dir that always returns NotFound. It now goes through installed_copies, so any hash-dir copy that differs from the baseline flags the patch. inLock is now written only when the cwd's Gradle locks were actually read. A global run inside a Gradle build reads them too, and a run outside one has no inLock at all, where it used to report false for every package. gradle_user_home_differs is skipped under --global-prefix, because the user home is not used then. The Gradle warning codes now carry a level in the JSON warnings[]: info for gradle_maven_local_undetermined and gradle_user_home_differs, warn for gradle_build_ignores_m2. Human mode prints the info ones as "Note:" and leaves them out under --silent. The real-Gradle capstone now takes the package path from the crawler, over the roots this build scans, and expands that path rather than one it built itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Agent mode could only patch files that sit directly in a version directory, so member-keyed Maven records (#264) were unpatchable and a Gradle files-2.1 version dir verified nothing. patch/jvm_jar classifies a record as leaf- or member-keyed, verifies jar members against an explicit jar name (so hosted copies check their suffixed jar), swaps in the patch service's build of the whole jar after checking every unpatched member is upstream's, and keeps the original under .socket/jvm-originals/ where blob cleanup never looks. Rollback restores that backup byte for byte, or re-downloads a Gradle copy's jar and accepts it only when it hashes to the copy's hash directory. Variant selection, verify_patch_record, judge_installed_record and the hosted copy check now expand Gradle version dirs through installed_copies, and VEX takes every installed copy of a Maven purl and reports the ones that do not verify. A Gradle cache copy no longer counts against a vendored entry, since the vendored build never reads it. Maven ~/.m2 .sha1/.md5 files are rewritten only when they matched the pre-patch bytes, and Gradle cache writes carry Info advisories about refreshes, daemons and the shared user home. The registry fetch helpers in vendor/maven_repo.rs become pub(crate) for the upstream fallback. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vendored Gradle reported success on builds it did not actually patch: a pom.xml next to a Gradle build skipped the Gradle side (#395), a subproject was wired as its own root (#428), an autocrlf checkout failed --check and left the script behind on revert (#429), exclusiveContent and Android checks only looked at the root build script (#461), pgp-only verification entries broke the build (#487), ranges downgraded to an unpatched release because the tree listed no versions (#511), and a declared classifier stopped resolving (#533). - detect() reports every build of the root (Detected{maven, gradle}); a mixed root is planned through both planners in one transaction, and a refusal of either writes nothing. Revert, --check, VEX liveness and repair run both halves. - not_build_root refuses vendor and repair from a Gradle subproject, from includes that cannot be read, and from a project configured by an ancestor settings file. - Owned text (script, index, .gitattributes, derived metadata, .mvn/maven.config) is compared line-ending blind; new owned .socket/gradle/.gitattributes and .socket/vendor/.gitattributes keep them out of EOL conversion. A vendor-created settings file is deleted once only whitespace is left. - The planner builds crate::gradle::graph::ScriptGraph and refuses a conflicting exclusiveContent or an Android/KMP plugin anywhere it can follow (subprojects, convention plugins, apply from, catalogs), naming the file; what it cannot follow is degraded. - A pgp-only metadata entry gets a sha256 beside its <pgp>; --check and the parent-chain warning require a checksum when metadata verification is on. - Each vendored GA gets a derived maven-metadata.xml (Gradle version order, no lastUpdated), recomputed on revert and deleted with the GA's last row. A range is noted; one admitting no vendored version refuses. - JvmPatch.extra_artifacts serves declared classifiers (and sources when found) from the tree; a declared one that cannot be sourced refuses. - Upstream files come from the crawler's directory and every local JVM cache (jvm_cache::locate_artifact over all_local_roots), Gradle copies authenticated by their hash directory. - The settings helpers WP4 needs are pub(crate) and parameterized by WiringTarget; the vendored defaults keep the output byte-identical. The lexer is crate::gradle::dsl. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Agent mode patched only the first copy of a Maven purl, and Gradle version directories were joined like ~/.m2 ones, so a GAV cached by Gradle stayed vulnerable while apply reported success (#551). The Maven lookup now returns every cache holding a copy (get_maven_copy_paths), and a JvmScope sorts them into the copies a build consumes, the read-only cache, and an ~/.m2 a Gradle-only build never reads. apply patches each consumed copy, expanding Gradle version dirs into the hash dirs holding the record's files, and swaps the whole jar for member-keyed records (#264). Each Gradle hazard has its own code: gradle_verification_metadata_present refuses with nothing written, gradle_build_ignores_m2 fails an ~/.m2-only GAV, gradle_ro_cache_shadows fails a run with a read-only copy, gradle_copy_unexpected_bytes leaves a pristine download of other bytes alone, and gradle_transform_copy_stale fails a copy whose derived transforms still hold the pristine jar. rollback groups by (base purl, copy), restores each hash dir and checks the restored bytes hash to their directory (gradle_rollback_hash_mismatch otherwise), restores whole jars from their backups, and puts ~/.m2 checksum files back. get narrows release variants over every copy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 7, 2026
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 65112a8)
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #769 Assisted-by: Claude Code:claude-opus-5-5 * Re-vendor Pipenv locks to a newer patch A Pipenv project vendored at one patch never moved to a newer patch for the same package: the re-vendor refused with pypi_pipenv_source_already_exists and the run exited 1, although the dry run previewed would_revendor. When the vendor ledger records the Pipfile.lock entry the older patch wrote, and that entry is unchanged, it is now rewired in place to the new wheel. The record carries the older entry's pre-vendor registry original forward, so vendor --revert still restores the user's pin. Without that record, or after an edit, it still refuses as before. Refs #769 Assisted-by: Claude Code:claude-opus-5-5 * Re-vendor PyPI installs from an older patch When a venv was installed from the vendored wheel of an older patch (pipenv sync after vendoring), re-vendoring to a newer patch skipped the package as package_not_installed and exited 1: the installed files are the old patch's bytes, so they failed the new patch's installed-variant check. When the vendor ledger holds exactly this package at an older patch uuid, such an install is now treated like a lock-only checkout: the pristine wheel comes from the lock, registry or patch service, and the package is re-vendored. The service download plan makes the same call. Fixes #769 Assisted-by: Claude Code:claude-opus-5-5 * Keep the ledger-less Pipenv wrappers test-only check_target_guards and wire_pipenv now have no production caller (the vendor flow passes the ledger through the _superseding variants), so clippy flagged them as dead code. Compile them for tests only and point the docs at the variants production uses. Refs #769 Assisted-by: Claude Code:claude-opus-5-5 * Port #851's vex alias test fix Main has been red since 4646693 (#605): two commands::vex_consumed tests built for #738 assume the name-keyed resolver never returns npm-aliased copies, which #605 changed. This is the same test-only change as #851 and becomes a no-op once that lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VSXCFoPbraq7rNKJpXEP2n * Port #878's Gradle digest routing Main is red since 1714299 (#865): its production_digests_go_through_the_helpers guard flags the inline digests that #646 added in gradle_cache.rs, jvm_jar.rs and sidecars/maven.rs. This is the same change as #878 and becomes a no-op once that lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VSXCFoPbraq7rNKJpXEP2n --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #410 Assisted-by: Claude Code:claude-opus-5-5 * Fix pip rollback refusing all-hosted requirements Hosted rollback, remove and the hosted-to-vendored takeover refused a requirements.txt in which every requirement was a hosted pin (a lone `six==1.16.0`, or one beside `-e .`). They couldn't tell whether the original line used pip's hash-checking mode, so the only way back was version control. The restore now counts an editable line as unhashed evidence (pip refuses editables in hash-checking mode). When no other line settles the mode, it reads the hosted line itself: the rewriter writes `--hash` only into an already hashed file and otherwise pins by the url's `#sha256=` fragment. With nothing else in the file to conflict with, either restored form installs. Fixes #410 Assisted-by: Claude Code:claude-opus-5-5 * Update restore golden for sole-pin requirements The golden test asserted that a requirements.txt holding only the hosted pin is refused as ambiguous, which is the #410 bug. It now asserts that both the unhashed and hashed sole-pin files round-trip, and keeps the mixed hashed/unhashed refusal. Refs #410 Assisted-by: Claude Code:claude-opus-5-5 * Fix vex alias tests broken by store-copy merge #605 taught the name-keyed npm resolver to probe bundled store trees, so it now finds aliased copies (node_modules/lp) and a nested host's store peers itself. Two vex_consumed tests from #738 assumed that set never held aliases, so main's CI went red after both merged. The tests now feed the alias-free set explicitly to keep covering alias expansion, and also check the resolver's own set reaches the same copies with no duplicates. No production code changes. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 40dac07) * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #364 Assisted-by: Claude Code:claude-opus-5-5 * Refuse hosted yarn classic with an offline mirror A yarn classic project that sets yarn-offline-mirror (in .yarnrc or .npmrc) had its lock rewired to the hosted tarball. Yarn looks mirror tarballs up by file name, and the hosted one has the same name as the upstream tarball already in the mirror, so every install got the unpatched bytes and failed the integrity check (or, offline, never found the patched tarball) while the scan reported success and VEX attested the patch. The hosted rewrite now leaves yarn.lock untouched in that case, warns with redirect_yarn_classic_offline_mirror and points to vendored mode, which works with a mirror. The dependency is not counted as redirected or attested. Both config files are read only beside a classic lock. Fixes #364 Assisted-by: Claude Code:claude-opus-5-5 * Keep mirrored yarn classic vendored on takeover A vendored-to-hosted takeover reverted the vendored yarn classic wiring before the hosted rewrite refused the offline mirror, leaving the package patched in neither mode. The takeover now checks the mirror first and keeps the package vendored. Adds a real-yarn e2e (yarn 1.22.22, populated mirror) showing the scan refuses, writes no attestation, and fresh installs still work online and offline. Refs #364 Assisted-by: Claude Code:claude-opus-5-5 * Document the yarn classic offline mirror refusal Refs #364 Assisted-by: Claude Code:claude-opus-5-5 * Re-bless pdm and poetry rewrite goldens These goldens hash the Debug text of the whole rewrite result, which now carries the empty refused_yarn_classic_uuids set. With that field stripped from the text, the old goldens still match every case, so only the output digests change; case keys and inputs are identical. Refs #364 Assisted-by: Claude Code:claude-opus-5-5 * Fix mirror e2e on yarn releases before 1.7 yarn 1.0 to 1.6 install nothing from an offline mirror even without socket-patch, so the fresh-install leg of the new mirror e2e failed on the yarn-classic 1.0.2 and 1.6.0 matrix legs. Those releases now pin that known limitation; the hosted refusal is still checked on every release. Refs #364 Assisted-by: Claude Code:claude-opus-5-5 * Detect a .yarnrc offline mirror written with a colon yarn 1's .yarnrc parser ends an unquoted key at ':', so `yarn-offline-mirror: ./mirror` and `yarn-offline-mirror:./mirror` configure the mirror just like `yarn-offline-mirror ./mirror`. The mirror check only split on whitespace, so either spelling slipped through and hosted mode still rewired the lock, reproducing #364. Refs #364 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uQyhodCtdJGrKaD7AAV1n * Port vex alias test fix from #851 main has been red since #605 taught the name-keyed resolver to return npm-aliased copies, which broke two vex_consumed tests added by #738. Port #851's test update so this PR's CI goes green; it no-ops once #851 lands on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uQyhodCtdJGrKaD7AAV1n * Port Gradle digest-helper fix from #878 main has been red since #865 added a check that production code computes digests through utils::digest, while #646's Gradle code still hashes inline. Port #878's change so this PR's coverage and test-release go green; it no-ops once #878 lands on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uQyhodCtdJGrKaD7AAV1n --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #367 Assisted-by: Claude Code:claude-opus-5-5 * Keep a project's own bun patch when rewiring Bun Bun applies a patch from `bun patch` (package.json patchedDependencies, mirrored in bun.lock) only while the lock resolves the package to its registry name@version. Hosted and vendored mode moved that entry to a hosted URL or a vendored tarball, so every later install silently dropped the user's own patch while socket-patch reported success. Hosted mode now leaves such a package on its registry entry in both bun.lock and bun.lockb, warns redirect_bun_patched_dependency_skipped naming the key, and keeps the in-run VEX from assuming the Socket patch applied. Vendored mode refuses it with vendor_lock_entry_unsupported before any write or download. Other packages in the lock are still rewired. Fixes #367 Assisted-by: Claude Code:claude-opus-5-5 * Format the new Bun patch tests Assisted-by: Claude Code:claude-opus-5-5 * Never confirm a Bun package the user patched Bugbot review of #873 found two gaps in the bun patch guard. A text bun.lock only reached the root package.json through its workspaces section, so a lock without one never saw the patchedDependencies keys and rewired the package anyway. The manifest is now read beside either Bun lock. A package left on the registry could still be counted as switched when a sibling package-lock.json took the hosted URL, although Bun keeps installing the registry bytes. Such uuids are now recorded as refused and never confirmed. Refs #367 Assisted-by: Claude Code:claude-opus-5-5 * Keep rewrite goldens stable with the new field The refused-Bun uuid set added to RewriteResult changed the serialized and Debug output that the redirect equivalence goldens hash. The set is now left out of serialization when empty, like the other per-ecosystem uuid sets, and the two goldens that hash the Debug output (poetry, pdm) are re-blessed. Only their output digests change; every case and input digest is identical. Refs #367 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) * Read a JSONC package.json for Bun patch keys Bun accepts comments and trailing commas in package.json. The patchedDependencies reader parsed it as strict JSON, so such a manifest yielded no keys. A bun.lockb project has no text mirror to fall back on, so the project's own bun patch could still be rewired away. The reader now strips JSONC comments and trailing commas before parsing, leaving string contents untouched. Refs #367 Assisted-by: Claude Code:claude-opus-5-5 * Skip a BOM before reading Bun patch keys A Windows-saved package.json can start with a UTF-8 byte order mark, which Bun ignores but serde_json rejects. The reader found no patchedDependencies keys in such a manifest, so a bun.lockb project could still lose its own bun patch. The mark is now stripped first, as the crate's other manifest readers do. Refs #367 Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #826 Assisted-by: Claude Code:claude-opus-5-5 * Keep gem declarations sharing a line with ; A Gemfile line like `gem "a", "1"; gem "b", "2"` had its second declaration deleted when socket-patch redirected or vendored gem "a", because the rewrite replaces the whole line and the safety check did not know that `;` starts a new statement. The next frozen `bundle install` then failed. Such lines are now refused with a warning and left untouched. A declaration ending in a bare `;` (optionally followed by a comment) was refused as "continues on the next line" since #637. It is complete, so it is rewritten again, without the `;`. Fixes #826 Assisted-by: Claude Code:claude-opus-5-5 * Use the reported line shape in the ; e2e test The `;`-joined fixture had no version argument, so the old check already refused it as "unexpected tokens" and the test passed without the fix. Use `gem "x", "v"; gem "y", "v"` from #826, which the old code rewrote and lost the second gem. Assisted-by: Claude Code:claude-opus-5-5 * Port #878: route Gradle digests through helpers main is red: #646 added inline sha1/sha256 calls that #865's production_digests_go_through_the_helpers guard rejects. This ports the fix from #878 so this PR's coverage job can go green. It becomes a no-op once #878 lands. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #760, #762 Assisted-by: Claude Code:claude-opus-5-5 * Test that a Poetry/PDM lock renders once per scan Hosted scans rewrite poetry.lock and pdm.lock once per patched package, rendering and re-parsing the whole lock each time. Count the engine's whole-lock renders and require one per lock for a dozen patched packages. Both tests fail today with 12 renders. Refs #760, #762 Assisted-by: Claude Code:claude-opus-5-5 * Rewrite each Poetry/PDM lock in one pass A hosted scan rewrote poetry.lock and pdm.lock once per patched package, and every rewrite rendered and re-parsed the whole lock. A project with a dozen patches paid for a dozen full parses, which made Poetry and PDM scans 3.5-4.5x slower per package than other managers. The shared lock-splice engine now plans every package against one parsed lock, applies all the changes, renders and re-parses once, and splices each package's changed fragments into the original text. The result is checked against the rendering byte for byte. When a lock mixes line endings, a package is rewritten twice, or any check fails, the rewrite falls back to the old package-by-package path, so output and recorded edits never change. Differential tests run both paths over every Poetry and PDM lock generation, LF, CRLF and mixed, with refusals, missing packages and re-runs mixed in, and require identical text and per-package results. Fixes #760, #762 Assisted-by: Claude Code:claude-opus-5-5 * Read PDM lock_version from the parsed original The rewrite never changes [metadata] lock_version, so read it from the parse the presence probe already took instead of parsing the output. Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 * Drop the unrelated formatting sweep Running cargo fmt over the whole workspace reformatted 117 files this PR does not otherwise touch, because main is not rustfmt-clean and CI does not check formatting. Restore those files to main and keep the diff to the Poetry/PDM rewrite and the ported digest fix. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start refactor for #845 Assisted-by: Claude Code:claude-opus-5-5 * Bound crawler and tool probes by one deadline A version-manager shim that never answers (`gem`, `python3`, `npm`, `composer` behind rbenv/asdf, a Ruby waiting on a network gem home) used to hang `scan`, `apply`, `vex` and every other crawling command forever with no output: the crawler probes waited on `output()` with no deadline. Every probe now runs through one `utils::process::output_within` primitive: null stdin, captured stdout, dropped stderr, and the child killed and reaped at the deadline without waiting on a grandchild that still holds the pipe. Crawler probes get the same 10 s budget that the Pipenv and Hatch version probes and the self-update `--version` check already used, and those three sites drop their hand-rolled `tokio::time::timeout` + `kill_on_drop` blocks for it. Refs #845. Assisted-by: Claude Code:claude-opus-5-5 * Port #878: Gradle digests through utils::digest `main` fails `utils::digest::tests::production_digests_go_through_the_ helpers` because #646 left inline sha1/sha256 calls in `gradle_cache.rs`, `jvm_jar.rs` and `sidecars/maven.rs`, which turns `test`, `test-release` and `coverage` red on every PR. This is #878's change verbatim; it no-ops once #878 merges. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Cancel superseded npm/pnpm/Pipenv PR runs npm-compatibility, pnpm-compatibility and pipenv-compatibility had no concurrency group, so every push to a PR left the previous run's full matrix (11, 26 and 6+ jobs) running to completion against a commit nobody will merge. Over the last 100 PR runs of each (about 8 hours), 52 runs were superseded while still running and spent ~445 job-minutes after the newer push landed, competing for runners with the live runs. Group PR runs per PR number with cancel-in-progress, as ci.yml and the other compatibility workflows already do. Every non-PR event gets its own group (run_id) so no main push or dispatch is ever cancelled, not even while pending behind another run in the group. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQTUzCY6pkBLc3BNJhz9Nu * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Run pnpm install-proof as one job per Node The pnpm install-proof matrix spawned 25 single-version jobs (one per pnpm/Node pair) whose real work is ~20 s each. Most of each job was runner setup, and any one leg that never got a runner left the run red: on 2026-10-05 20/28 pnpm runs failed, every failed leg checked being an ubuntu-latest job cancelled with no runner and no log. Group the legs by Node runtime (10, 16, 24): each job installs its pnpm versions, then runs both pinned suites per version in turn with a per-version TMPDIR so the shared cache sandbox starts empty, as it did on a fresh runner. Every pnpm/Node pair still runs on every PR and main push; a failure is reported per version via ::error. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uej6tnJfjRU8NCUz2jdDG4 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #915 Assisted-by: Claude Code:claude-opus-5-5 * Patch the gem Bundler loads under path.system A project that switched from vendor/bundle back to system gems (`bundle config set path.system true`, or BUNDLE_PATH__SYSTEM=true) usually still has the old gitignored vendor/bundle. The gem crawler always counted that leftover store and, because it held gems, stopped looking in the `gem env` homes. Agent apply then patched only the unused copy, and vex attested not_affected while Bundler kept loading the unpatched system gem. The crawler now works out which Bundler settings tier decides the install path (local config, then environment, then global config) and, when that tier sets a truthy path.system, skips the default vendor/bundle root so the system gem homes are crawled. path.system values now follow Bundler's own boolean coercion, so "1" or "yes" count as true too. Fixes #915 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) * Skip env bundle path shadowed by path.system When the Bundler tier that wins sets path.system, Bundler also ignores an env BUNDLE_PATH below it or beside it. If that value named the leftover vendor/bundle, the crawler still probed it as the default root and hid the system gem homes again, so apply and vex kept targeting the unused copy. The env root is now skipped in that case too. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #908, #521 Assisted-by: Claude Code:claude-opus-5-5 * Restore berry and vlt pins from project registry Hosted rollback and remove looked up a package's version document on the default registry (npmjs or SOCKET_NPM_REGISTRY) only. On a project that installs from a mirror whose tarball URLs are off the usual path, that broke the restored lock: - yarn berry wrote a bare npm: locator, so a cold-cache install asked the mirror for a path it never serves and failed with a 404 (#908). - vlt rebuilt slot [3] as <registry>/<name>/-/<leaf>-<ver>.tgz instead of the URL the registry advertises, which vlt ci can 404 on (#521). The restore now reads the document from the registry the project resolves the package against (.yarnrc.yml npmRegistryServer, the vlt node's registry) and vlt takes slot [3] from its dist.tarball. If that registry can't be read (for example it needs credentials), the old default-registry lookup is used and upstream_registry_fallback warns. Fixes #908 Fixes #521 Assisted-by: Claude Code:claude-opus-5-5 * Name the per-registry npm cache type Keeps clippy's type_complexity lint quiet for the restore client's registry-keyed version-document cache. Assisted-by: Claude Code:claude-opus-5-5 * Keep npmScopes packages on the default lookup A scoped package in a .yarnrc.yml with an npmScopes block may resolve against its scope's registry rather than npmRegistryServer, so berry restore keeps reading its document from the default registry, as before, instead of asking a registry that may not host it. Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main's test suite is red: the Gradle cache, jar and Maven sidecar code from #646 hashes inline, which the digest guard test from #865 forbids, so coverage and the macOS/Windows test jobs fail on every PR. This is the same change as #878, ported so this PR can go green; it no-ops once #878 lands. Assisted-by: Claude Code:claude-opus-5-5 * Serialize berry checksum tests that read SOCKET_NPM_REGISTRY The npm dist cache is now keyed by registry base, and these two tests seed it under npm_registry_base(), which reads SOCKET_NPM_REGISTRY. Serial vlt/bun tests set that variable, so when one ran in parallel the lookup key no longer matched the seeded entry and the test fetched left-pad from the other test's mock server (404). That is the test (windows-latest) failure on 48798c4. Serializing them with the env-mutating tests closes the race. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Bench: add gradle hosted and rescan scenarios #646 gave Gradle builds a hosted mode: scan crawls Gradle's modules-2/files-2.1 cache, pins suffixed versions in gradle.lockfile and wires the build through an owned settings script and index under .socket/gradle/. None of that was benchmarked; the maven scenarios only reach the pom.xml + ~/.m2 path. The gradle fixture is a single-project Groovy build with dependency locking (1000 locked artifacts, 25 patched direct deps), its cache under the fixture's GRADLE_USER_HOME with jar and pom in separate sha1 dirs. The Maven-coordinate generator, pom writer and maven2 grant builder are shared with the maven fixture, whose bytes are unchanged. The grant's indexUrl is https because the Gradle planner refuses anything else; scan never fetches it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) * Bench: add hatch hosted and rescan scenarios Hatch hosted mode (#680, #743) rewrites pyproject.toml and hatch.toml in place, with no lockfile, through utils::hatch::plan. No scenario exercised that rewriter: hatch.toml is a HOSTED pypi input, and a hatch project with no lock fell through every existing pypi fixture. The fixture is a lockless hatchling app. Direct deps go in [project], and a hatch.toml default env (in-project .venv) pins every patched transitive, since hosted Hatch only redirects deps a Hatch table declares. A scan rewrites both files and adds [tool.hatch.metadata] allow-direct-references. It is sized at 1000 packages / 25 patched so a scan takes about 75-85 ms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
) * Start fix for #926 Assisted-by: Claude Code:claude-opus-5-5 * Match PyPI names in get by their PEP 503 form `socket-patch get ruamel.yaml` (or `typing_extensions`, or `Typing.Extensions`) reported "No packages matching" and exited 0 for an installed, patchable package. The package-name search only lowercased the query, but the crawler stores PyPI names in PEP 503 form (`ruamel-yaml`), so any spelling with `.`, `_` or a run of separators never matched. Users who copy a name from requirements.txt or `pip list` were told nothing could be patched. PyPI packages are now compared with both the query and the name canonicalized per PEP 503, for exact, prefix and contains matches. npm and other ecosystems keep the plain case-insensitive compare, since `_` and `.` are distinct characters in their names. Fixes #926 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #504, #947 Assisted-by: Claude Code:claude-opus-5-5 * Test Pipenv crawl never reads the system Python A Pipenv project with no Pipenv venv yet must not have the OS Python's site-packages crawled as if they were the project's: agent mode patched them in place (#504), and vendored mode tried to vendor system-only packages into Pipfile.lock and exited 1 (#947). Replace the test that pinned the global fallback for a Pipfile marker with one asserting the opposite, and add CLI scans for agent, hosted and vendored modes. Assisted-by: Claude Code:claude-opus-5-5 * Stop Pipenv projects falling back to system Python When a Pipenv project had no Pipenv venv (a fresh checkout before pipenv install, or a project that only has a plain venv/), scan read the OS Python's site-packages instead. Agent mode then patched the system Python in place and VEX attested the project as fixed (#504); vendored mode tried to vendor system-only packages and failed with a misleading 'run pipenv lock' error (#947). A Pipenv project's env is only ever the one Pipenv resolves, so an empty result there is final. Lock-only packages still come from Pipfile.lock. Fixes #504 Fixes #947 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Ported from #878 so CI on this PR runs against a green base; it no-ops once #878 lands on main. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #956 Assisted-by: Claude Code:claude-opus-5-5 * Quote scoped names in pnpm 7/8 vendored locks Vendoring a scoped package (@scope/pkg) into a pnpm 7 (lock 5.4) or pnpm 8 (lock 6.0) project wrote `name: @scope/pkg` into the rekeyed packages entry. A bare `@` cannot start a YAML scalar, so pnpm refused the whole lock with ERR_PNPM_BROKEN_LOCKFILE: every frozen install failed after a scan that reported success, and lock-only VEX kept attesting not_affected from a lock pnpm could not read. The name is now written through the shared YAML scalar quoting, which gives `name: '@scope/pkg'`, byte-identical to what pnpm 7.33.7 and 8.15.9 serialize themselves for the same override. Tests: a byte-exact unit oracle captured from real pnpm 7/8 for @isaacs/string-locale-compare (vendor, in-sync re-run, revert), and scoped real-pnpm lifecycle legs (frozen install, moved checkout, manifest-less VEX, revert) in e2e_vendor_pnpm_build, also run in the pinned pnpm 7/8 matrix. Fixes #956. Assisted-by: Claude Code:claude-opus-5-5 * Skip scoped legacy leg on pnpm 8.0.0-8.1.0 pnpm 8.0.0 and 8.1.0 refuse their own lock for a scoped file: tarball override under --frozen-lockfile (ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY on the key they just wrote); 8.1.1 fixed it. Measured with real pnpm on Node 16: the lock pnpm itself writes fails the same way, so no vendored scoped lock can pass there. The pinned matrix keeps the unscoped leg on those versions and runs the scoped leg everywhere else. Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. (cherry picked from commit 659ac2c) Ported from #878 so this PR's CI is green while main's digest guard test is red; it no-ops once #878 lands. Assisted-by: Claude Code:claude-opus-5-5 * Re-vendor rewrites a stale unquoted scoped name edit_packages treated a packages entry as in sync once its file: key and resolution matched, without looking at name:. A lock vendored by a release before the #956 fix still carries `name: @scope/pkg`, which pnpm 7/8 can't load, so a later vendor reported the package already vendored and left the lock broken. The in-sync check now also requires the canonical quoted name: line, so the old spelling is rewritten like any other stale wiring. The new test revendor_heals_an_unquoted_scoped_name fails without this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UepoBazrbnBjy7HkD9YVJN --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #900 Assisted-by: Claude Code:claude-opus-5-5 * Name the real cause when vendor --check fails `vendor --check` reported every dead vendored entry as "no lockfile or config references .socket/vendor/... any more; re-run `socket-patch vendor`". In two common cases that was false and the remedy did nothing, so the CI gate stayed red for good: - Another lock (e.g. package-lock.json beside a wired yarn.lock or bun.lock) resolves the same version from the registry. The check now says the wiring is contested, names both locks, and says to delete the lock the project does not install from. - The dependency left the lock (`npm uninstall` or an upgrade). The check now says the dependency was removed and points at `socket-patch scan --mode vendored --prune`, the command that reverts the entry. This matches scan's own hint. Discovery now keeps the refs it drops as contested, so callers can name the contesting lock. `vex`'s vendor_unwired phrase no longer claims nothing wires the artifact when the cause is a contest or a removed dependency. Fixes #900 Assisted-by: Claude Code:claude-opus-5-5 * Document vendor --check cause-specific reasons Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #974 Assisted-by: Claude Code:claude-opus-5-5 * Run the musl binary on musl hosts in npm wrapper Yarn classic ignores the `libc` field, so on Alpine it installs both the -gnu and the -musl platform package. The npm wrapper always took the first package that resolved (-gnu), whose glibc binary cannot start on musl, and then exited 1 without printing anything. Every socket-patch command failed silently in yarn classic projects on Alpine and in node:*-alpine CI images. The wrapper now detects the host libc (Node's runtime report, then the musl loader probe scripts/install.sh uses) and tries the -musl package first on musl. If a binary cannot be spawned it tries the next installed candidate, and if none can run it prints the spawn error instead of exiting silently. Fixes #974 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
…884) (#901) * Start fix for #884 Assisted-by: Claude Code:claude-opus-5-5 * Refuse hosted runs from npm/yarn/bun members A hosted scan or get run from a member of an npm, yarn (classic or berry) or Bun workspace found the member's copy of a patched package, saw no lockfile in the member directory, pinned nothing and exited 0 with "success". The package manager then installed the unpatched copy from the workspace root's lockfile, and the only hint was a warning about a missing package-lock.json. The workspace-member pre-check only knew about pnpm and cargo. It now also finds the nearest ancestor package.json whose "workspaces" list matches the member directory. If that root holds a package-lock.json, npm-shrinkwrap.json, yarn.lock, bun.lock or bun.lockb, the run is refused before anything is written with redirect_workspace_lockfile_elsewhere, and the message names the workspace root to run from. Vendored mode already refused this layout. Fixes #884 Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 * Follow nested workspaces to the outer lock A workspace root with no lockfile of its own can itself be a member of an outer workspace (yarn berry's nested worktrees), where the outer root holds the lockfile both use. The member check stopped at the inner root and let the hosted run report success with nothing pinned. It now keeps walking with the inner root as the member and refuses at the outer root. Refs #884 Assisted-by: Claude Code:claude-opus-5-5 * Stop the member walk at a nested pnpm root A pnpm workspace nested inside an outer yarn or npm workspace owns its members: pnpm installs them from the nested pnpm-lock.yaml. The member walk treated that nested root as lockless and went on to the outer root, so the refusal named the wrong directory to run from. The walk now stops at a root holding any npm-family lock (pnpm, vlt, Rush) and leaves it to the pnpm check, which names the right root. Refs #884 Assisted-by: Claude Code:claude-opus-5-5 * Refuse at the nearest workspace lock root The previous change stopped the member walk at a nested root holding a pnpm, vlt or shrinkwrap.yaml lock and left it to the pnpm check. That check only knows pnpm workspaces with pnpm-workspace.yaml or lockfile-dir, so a stray pnpm-lock.yaml there could still let a hosted run from the member report success with nothing pinned. The pnpm check now runs first, so a pnpm workspace still gets its own precise message. The package.json walk then refuses at the first matching root that holds any npm-family lock, naming that root. Only a Rush root, whose locks live under common/config, ends the walk without a refusal. Refs #884 Assisted-by: Claude Code:claude-opus-5-5 * Prefer the nearer root over an outer pnpm one When a yarn or npm workspace sits inside a pnpm workspace, the member's lockfile is the nearer one. The pnpm check ran first and named the outer pnpm root, so a follow-up run from there would rewrite pnpm-lock.yaml and leave the member's real lockfile unpatched. The member check now weighs both roots and names the nearer one. When they are the same directory, pnpm's own message wins. Refs #884 Assisted-by: Claude Code:claude-opus-5-5 * Count only npm, yarn and Bun locks at roots pnpm reads only pnpm-workspace.yaml and vlt only vlt.json, so a pnpm or vlt lock sitting at a package.json "workspaces" root does not govern that root's members. Counting such a stray lock as ownership let it beat the outer pnpm workspace that really installs the member, and the refusal pointed at a directory whose run would rewrite the wrong lockfile. The workspaces walk now counts only npm, yarn and Bun locks. Roots that pnpm governs are left to the pnpm check, and when both kinds govern a member the nearer root still wins. Refs #884 Assisted-by: Claude Code:claude-opus-5-5 * Drop unrelated rustfmt churn from the #884 fix f92cb6a ran a workspace-wide cargo fmt, reformatting 118 files that the fix does not otherwise touch. That buried the real change in ~2,300 lines of formatting diff and invites merge conflicts with every other open PR. Restore those files to their merge-base versions; each was checked to be rustfmt-equivalent to its main version, so behavior is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 7, 2026
* Start fix for #701, #932 Assisted-by: Claude Code:claude-opus-5-5 * Stop hosted PyPI pinning platform-only wheels When the patch service granted a PyPI patch as a platform- or ABI-tagged wheel (for example cp311 manylinux), hosted scan pinned that one wheel into the project's cross-platform lock: uv.lock, PEP 723 script locks, pylock.toml, Pipfile.lock, poetry.lock, pdm.lock, requirements.txt or Hatch's pyproject. It reported success, but installs then failed on every other Python version, OS and architecture, and hosted rollback refused to undo it. Hosted mode now checks the granted wheel's tags once, where every PyPI lock writer is dispatched. A platform-specific wheel is withheld from all of them and reported with a redirect_pypi_platform_wheel warning, the same way hosted gem refuses platform gems. Nothing is written or attested for that patch; other patches in the run are unaffected. The tag rule is the one vendored mode already uses for vendor_platform_locked, now shared between both modes. Fixes #701, #932. Assisted-by: Claude Code:claude-opus-5-5 * Keep vendored PyPI patch on a platform grant A vendored PyPI package that a hosted scan takes over is reverted to its registry entry first, and only then pinned to the hosted wheel. With platform-tagged hosted wheels now refused, that order would strip the live vendored patch and leave the package unpatched. The takeover now asks the same platform-wheel check before it reverts anything, so the package stays vendored and patched, and both the wet run and the dry run name redirect_pypi_platform_wheel as the reason. Refs #701, #932. Assisted-by: Claude Code:claude-opus-5-5 * Format the takeover test's hosted route Assisted-by: Claude Code:claude-opus-5-5 * Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c) * Check the Pipenv refusal exit code without VEX The new Pipenv platform-wheel test asserted exit 0 on a run that also asked for --vex. With nothing pinned, VEX correctly fails with manifest_not_found, so the run exits 1 and the coverage job failed. Assert the hosted refusal's exit 0 on a plain scan, then run --vex separately and check only that it attests nothing. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings Gradle up to parity with the other JVM ecosystems across all three patch modes.
What each mode now does for Gradle
~/.gradle/caches/modules-2/files-2.1, honoringGRADLE_USER_HOME) through the sharedjvm_cacheseam, soscan,get,applyandrollbackfind and patch Gradle-resolved artifacts in place..modulemetadata so Gradle's metadata-first resolution picks up patched artifacts.Dependency
Depends on SocketDev/depscan#27220 for serving suffixed
.modulefiles (and themavenModuleSha256grant identifier). Without it the CLI degrades gracefully and reportsredirect_gradle_module_metadata_unavailableinstead of failing.Test coverage
ci.yml: ubuntu x 4 Gradle majors (6, 7, 8, 9).gradle-compatibility.yml: 3 OS x 4 Gradle majors x 3 modes (agent / hosted / vendored).Notes
The sbt session (
feat/sbt-support) builds on the same sharedjvm_cacheseam introduced here.Closes #347
Closes #348
Closes #349
Closes #395
Closes #396
Closes #428
Closes #429
Closes #461
Closes #487
Closes #511
Closes #533
Closes #551
🤖 Generated with Claude Code
Note
High Risk
Changes how agent mode writes JVM/Gradle caches and how hosted mode rewrites Gradle settings and locks; mistakes could break builds or leave patches ineffective despite broad new e2e coverage.
Overview
Adds Gradle v5.0 across agent, hosted, and vendored modes, with contract docs and CI to match.
Agent (
apply) no longer treats Maven as a single install dir: it resolves a JVM scope, patches every consumed copy (~/.m2and Gradlefiles-2.1hash dirs), supports whole-jar member swaps via the patch service, and enforces Gradle-specific guards (dependency verification, read-only cache,mavenLocal()consumption, unexpected pristine bytes, transform/jar staleness, daemon locks). Blob-gap probing and vendor preverify walk Gradle hash dirs the same way.Hosted / vendor flows gain Gradle takeover preflight before vendored→hosted revert, a guard against creating
settings.gradleover an unreadable existing file, broader eject snapshots (owned Gradle scripts, wiring files, FIFO-safe reads), and hosted confirmation viaconfirmed_gradle_uuidsin the contract.CI introduces
jvm_toolmatrix legs, multi-suite e2e execution with per-suite pass checks and Gradle probe artifacts, bumps Gradle test versions (e.g. 7.6.6, JDK 21 on 8/9), and addsgradle-compatibility.ymlfor the full OS × Gradle × mode grid (nightly + path filters)..gitattributespins the hosted settings script as binary-safe.getnarrows release variants using all Maven install copies (select_installed_variants_any);removesurfaces nested rollback warnings.Reviewed by Cursor Bugbot for commit f4b4339. Configure here.
Generated by Claude Code