You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(ENG-15139): ask Maven, Gradle and sbt where they read the Cloudsmith tools - #595
cloudsmith credential-helper install maven, install gradle and install sbt put the Cloudsmith Maven tools in fixed locations: ~/.m2/repository, $GRADLE_USER_HOME or ~/.gradle, and ~/.sbt/1.0/plugins. If you moved one of these, the build tool looked in the other directory and did not find the extension or plugin. The CLI had already reported success, so the first sign of the problem was a failed build.
Before, with <localRepository>/opt/m2</localRepository> in ~/.m2/settings.xml:
$ cloudsmith credential-helper install maven
downloaded cloudsmith-maven-extension-0.2.0.jar to /home/me/.m2/repository/io/cloudsmith/cloudsmith-maven-extension/0.2.0
After:
downloaded cloudsmith-maven-extension-0.2.0.jar to /opt/m2/io/cloudsmith/cloudsmith-maven-extension/0.2.0
The CLI now asks each build tool where it reads its config, and does not parse the tool's config files itself. The tool applies its own settings files, environment variables, system properties and version rules.
Tool
The CLI runs
The tool reports
Maven
mvn -X -o -B cloudsmith-local-repository-probe
[DEBUG] Using manager … for <local repository>
Gradle
gradle -q --offline --no-daemon -I probe.init.gradle help
the Gradle user home, from a one-line init script
sbt
sbt -batch -no-colors cloudsmithGlobalPlugins
the global plugins directory, from a probe build.sbt (sbt 1 only). If a global plugin does not load, sbt 1 still names the directory in loading global plugins from …, so uninstall can remove a broken plugin
All three use the same code in maven_tools:
tool_output runs the tool with a 60-second timeout. It reads stdout and stderr together and removes colors. The tool runs in its own process group. On timeout or Ctrl+C, the CLI stops the tool and the JVM that the launcher script started. On timeout the command fails. It does not fall back to a default path in silence.
For a user-wide install, the tool runs in an empty temporary directory, so the config files of the current project do not apply. An empty .mvn and settings.gradle there stop Maven and Gradle from reading a project above the temporary directory. The tool then runs again in a subdirectory. If the two answers differ, the configured path is relative and changes with the directory of each build. The command then stops and tells you to set an absolute path or use --local.
If the tool is not on PATH, the CLI uses the old default location. If the tool is on PATH but reports nothing, for example a version-manager shim that works only inside one project, the CLI uses the default location and shows a warning that names it.
Maven also has these rules:
The unknown phase makes Maven stop before it runs a plugin, also when the POM has a <defaultGoal> or MAVEN_ARGS has goals.
With --local, Maven runs in the current directory, as a build from there does. The project's mvnw (mvnw.cmd on Windows) runs when there is one, else mvn.
--mavenrc uses the same repository, so the JAR path in ~/.mavenrc points at it too.
install and uninstall ask the tool. status, and so credential-helper list, keeps the default locations, so list does not start three JVMs.
Type of Change
Bug fix
New feature
Breaking change
Documentation update
Refactoring
Other (please describe)
Additional Notes
Each tool has one test that runs the real tool. The test skips when the tool is not installed or does not run. GitHub's Ubuntu and Windows runners have Maven and Gradle, so CI runs those tests. I ran all three locally with Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0, Gradle 9.8.1 and sbt 1.13.0. Typical time for install: Maven 1 s, Gradle 4 s, sbt 5 s.
The tools do not agree, which is why the CLI asks them and does not parse their files. With -Dmaven.repo.local=${user.home}/repo in .mvn/maven.config, Maven 3.9 and later use ~/repo. Maven 3.8 and earlier use a directory named ${user.home}/repo. sbt prints a relative sbt.global.base as it is, so the sbt probe prints the absolute path.
To check that the Maven probe runs no goal, I used Maven 3.8.8 with a POM whose <defaultGoal> is process-resources. With no goal, Maven ran maven-resources-plugin. With the unknown phase and MAVEN_ARGS=process-resources, Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0 stopped before any plugin ran. Maven does not accept goals in .mvn/maven.config.
Maven 3.10 reads user extensions only from ${user.home}/.m2/extensions.xml, also with -Dmaven.user.conf, so that path stays fixed.
An earlier version of this PR parsed the Maven config files in Python. Each review found another case where it did not match Maven, so that code is gone.
The mvn script expands MAVEN_OPTS without quotes, so a JAR path with a
space splits into two JVM arguments and every build fails. mvn.cmd on
Windows does not read ~/.mavenrc. Stop `install maven --mavenrc` in both
cases before it changes a file or downloads the JAR, and tell the user to
use --local or Maven 3.10. The Maven 3.9 warning on Windows now names
only --local.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itory
Maven loads a build extension from extensions.xml only from Maven Central
or the local repository. The installer always used ~/.m2/repository, so a
custom local repository made Maven miss the extension.
The installer now uses -Dmaven.repo.local from MAVEN_OPTS (and, with
--local, from .mvn/jvm.config and .mvn/maven.config), then
<localRepository> in ~/.m2/settings.xml, then ~/.m2/repository.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The Maven config parser treats commented repository settings as active and truncates valid paths containing spaces, allowing local installation to report success in a repository Maven will not use.
Maven 3.9 reads each line of .mvn/maven.config as one argument and skips
lines that start with #. The installer split the file on whitespace, so a
comment could set the local repository and a path with a space was cut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The installer misparses Maven 3.8 multi-option configuration lines, placing the extension in the wrong directory and reporting success even though Maven cannot find it.
Maven 3.8 splits .mvn/maven.config on whitespace, so one line can hold
several options. With --local, the installer now asks mvn -v for the
version when maven.config is not empty, and splits the file on whitespace
for Maven before 3.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With <localRepository>/opt/maven cache</localRepository>, install maven --mavenrc now downloads successfully but writes a class-path option that breaks Maven startup. add_block stores the path in MAVEN_OPTS, which the launcher expands unquoted: the generated option becomes two arguments (-Dmaven.ext.class.path=/opt/maven and cache/...jar). Preserve the JAR path as one JVM argument, or reject whitespace-containing paths before downloading or changing activation files and direct the user to an extensions.xml-based installation.
Gradle reports its user home and sbt 1 reports its global plugins directory,
so -Dgradle.user.home and -Dsbt.global.base now count. Maven, Gradle and sbt
share one way to run the tool and to find a relative path. status keeps the
default locations, so credential-helper list stays fast.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cloudsmith-iduffy
changed the title
fix(ENG-15139): put the Maven extension in the configured local repository
fix(ENG-15139): ask Maven, Gradle and sbt where they read the Cloudsmith tools
Nov 6, 2026
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Uninstall does not catch RelativeConfigPath, so relative Gradle or sbt configuration paths trigger unexpected-error handling and plain-text output instead of structured JSON with -F json.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human review is needed to assess probe edge cases that can leave custom-path sbt plugins enabled, child JVMs holding cache locks, or installers silently falling back to default paths.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The probes can inherit project-specific Maven configuration, leave child JVMs running after timeout, and silently fall back to a default path when initialization stalls.
…e probe
A timeout now stops the JVM that the launcher script started and fails the
command, so the CLI never falls back to a default path in silence. An empty
.mvn and settings.gradle stop Maven and Gradle from reading a project above
the temporary directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The test uses os.kill(child_pid, 0) to detect a running child, which incorrectly treats an unreaped Linux zombie as alive and can fail despite successful process-group termination.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human review is needed to assess a working-directory dependency that can cause tool detection to fail silently and select the default installation location instead of the configured directory.
When mvn, gradle or sbt is on PATH but reports no location, the command now
warns and names the default it uses. The executable path is made absolute
before the tool runs in the temporary directory. The empty .mvn and
settings.gradle move into the shared probe, and Gradle and sbt keep only
their probe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because cancellation and timeout cleanup can leave build processes running, while sbt plugin-loading failures can prevent uninstall from finding a custom installation path.
…emove
The probe runs in its own session, so Ctrl+C now stops it as well. sbt 1
names its global plugins directory before it fails to load a plugin there,
so uninstall can still find a plugin that does not load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Windows tests fail during cleanup because the patched subprocess method has an incompatible signature, and relative SBT paths can cause install/uninstall to operate in the wrong directory.
…s test
A relative path that a tool prints now resolves against the directory where
the tool ran, so the two probes see a relative global base of sbt. The
timeout test interrupts only the tool, so taskkill can run on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build tool can take a few seconds to start, so install and uninstall
now print what they ask it before they run it. The list help says that it
checks only the default Gradle and sbt locations.
Co-Authored-By: Claude Opus 5.5 <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
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.
Description
cloudsmith credential-helper install maven,install gradleandinstall sbtput the Cloudsmith Maven tools in fixed locations:~/.m2/repository,$GRADLE_USER_HOMEor~/.gradle, and~/.sbt/1.0/plugins. If you moved one of these, the build tool looked in the other directory and did not find the extension or plugin. The CLI had already reported success, so the first sign of the problem was a failed build.Before, with
<localRepository>/opt/m2</localRepository>in~/.m2/settings.xml:After:
The CLI now asks each build tool where it reads its config, and does not parse the tool's config files itself. The tool applies its own settings files, environment variables, system properties and version rules.
mvn -X -o -B cloudsmith-local-repository-probe[DEBUG] Using manager … for <local repository>gradle -q --offline --no-daemon -I probe.init.gradle helpsbt -batch -no-colors cloudsmithGlobalPluginsbuild.sbt(sbt 1 only). If a global plugin does not load, sbt 1 still names the directory inloading global plugins from …, souninstallcan remove a broken pluginAll three use the same code in
maven_tools:tool_outputruns the tool with a 60-second timeout. It reads stdout and stderr together and removes colors. The tool runs in its own process group. On timeout or Ctrl+C, the CLI stops the tool and the JVM that the launcher script started. On timeout the command fails. It does not fall back to a default path in silence..mvnandsettings.gradlethere stop Maven and Gradle from reading a project above the temporary directory. The tool then runs again in a subdirectory. If the two answers differ, the configured path is relative and changes with the directory of each build. The command then stops and tells you to set an absolute path or use--local.PATH, the CLI uses the old default location. If the tool is onPATHbut reports nothing, for example a version-manager shim that works only inside one project, the CLI uses the default location and shows a warning that names it.Maven also has these rules:
<defaultGoal>orMAVEN_ARGShas goals.--local, Maven runs in the current directory, as a build from there does. The project'smvnw(mvnw.cmdon Windows) runs when there is one, elsemvn.--mavenrcuses the same repository, so the JAR path in~/.mavenrcpoints at it too.installanduninstallask the tool.status, and socredential-helper list, keeps the default locations, solistdoes not start three JVMs.Type of Change
Additional Notes
Each tool has one test that runs the real tool. The test skips when the tool is not installed or does not run. GitHub's Ubuntu and Windows runners have Maven and Gradle, so CI runs those tests. I ran all three locally with Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0, Gradle 9.8.1 and sbt 1.13.0. Typical time for
install: Maven 1 s, Gradle 4 s, sbt 5 s.The tools do not agree, which is why the CLI asks them and does not parse their files. With
-Dmaven.repo.local=${user.home}/repoin.mvn/maven.config, Maven 3.9 and later use~/repo. Maven 3.8 and earlier use a directory named${user.home}/repo. sbt prints a relativesbt.global.baseas it is, so the sbt probe prints the absolute path.To check that the Maven probe runs no goal, I used Maven 3.8.8 with a POM whose
<defaultGoal>isprocess-resources. With no goal, Maven ranmaven-resources-plugin. With the unknown phase andMAVEN_ARGS=process-resources, Maven 3.6.3, 3.8.8, 3.9.16 and 3.10.0 stopped before any plugin ran. Maven does not accept goals in.mvn/maven.config.Maven 3.10 reads user extensions only from
${user.home}/.m2/extensions.xml, also with-Dmaven.user.conf, so that path stays fixed.An earlier version of this PR parsed the Maven config files in Python. Each review found another case where it did not match Maven, so that code is gone.
🤖 Generated with Claude Code