Repository navigation
Conversation
Leiningen 2.11.0 added the static-classpath task, which the built-in task list missed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entrypoint hardcodes Leiningen's tasks and aliases. Lein images with the entrypoint now list the tasks shipped in the Leiningen jar, plus its aliases, and check the installed entrypoint passes each one to lein, so a Leiningen upgrade that adds a task fails the image build. lein help is not a usable source: it omits static-classpath. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
I’ll do a more thorough review of this soon. In the meantime here’s some things Claude flagged (AIs reviewing AIs haha!) for your consideration. I know #1 is a known issue but I think it is worth some consideration of how to not break existing users if we can.
I wonder if it would make sense to work with lein & clj upstream to add a subcommand / flag discovery option that can’t execute code? Or maybe generate the static list of subcommands & flags when we build the project and find a better way to discover project-specific aliases, etc. at runtime? Just a thought.
Good catch though! We should definitely fix this.
The rest was... Generated by Claude Code
| exec "${entrypoint}" "$@" | ||
| fi | ||
|
|
||
| # Other commands run directly. Use explicit lein for custom tasks and aliases. |
There was a problem hiding this comment.
1. Breaking change for plugin tasks and project aliases. Previously lein <cmd> --help succeeded for plugin tasks (ring, cljsbuild, midje, figwheel, kibit, …) and project :aliases, so e.g. docker run -v $PWD:/app -w /app clojure:lein cljsbuild once ran lein cljsbuild once. Now it falls through to exec cljsbuild once and fails with not found (exit 127). The README note is the only notice existing CI pipelines get.
Generated by Claude Code
| exec "${entrypoint}" "$@" | ||
| ;; | ||
| esac | ||
| elif [ -f "$1" ] && ! type -P "$1" >/dev/null; then |
There was a problem hiding this comment.
2. Executable scripts named without a slash are evaluated as Clojure. type -P only searches PATH, so with -w /app and an executable /app/run.sh, docker run … clojure:tools-deps run.sh fails the type -P check, passes [ -f run.sh ], and runs clj run.sh, giving a confusing reader/compiler error. The comment and README say only non-executable Clojure scripts take this branch; adding [ ! -x "$1" ] (or similar) plus a test for an executable file named without ./ would cover it.
Generated by Claude Code
| # smoke test checks it); never probe a command with --help, which can evaluate | ||
| # code from the current project. | ||
| if [ "${entrypoint}" = lein ]; then | ||
| case "$1" in |
There was a problem hiding this comment.
3. Hand-maintained task list pinned to Leiningen 2.13.0. The lein version lives in cfg/build-tools, so a version bump leaves this list stale and the drift only shows up as failed image builds in CI. Could the list be generated instead, e.g. when shared-prereqs writes the entrypoint (it already substitutes @@entrypoint@@), or by running print-lein-commands during the Dockerfile build, so it always comes from the installed jar?
Generated by Claude Code
|
|
||
| status=0 | ||
| while IFS= read -r command; do | ||
| routed=$(PATH="$stub:/usr/bin:/bin" /usr/local/bin/entrypoint "$command" 2>/dev/null) || true |
There was a problem hiding this comment.
4. The entrypoint shares the loop's stdin. The entrypoint runs inside while read … <<EOF, so a fallback command that reads stdin can consume the rest of the command list. The loop then ends early and the check passes without testing the remaining commands. Suggest /usr/local/bin/entrypoint "$command" </dev/null.
Generated by Claude Code
| task-ns (symbol (str "leiningen." task))] | ||
| :when (try (require task-ns) | ||
| (ns-resolve task-ns (symbol task)) | ||
| (catch java.io.FileNotFoundException _ nil))] |
There was a problem hiding this comment.
5. Only FileNotFoundException is caught. If any leiningen/*.clj namespace in the jar fails to load standalone (missing optional dep, init that needs a project), require throws a Compiler$CompilerException. That aborts the whole listing, set -e stops the check script, and the image build fails even though routing is fine. Catching Exception (or skipping namespaces that aren't tasks) would avoid this.
Generated by Claude Code
| @@ -0,0 +1,20 @@ | |||
| set -e | |||
|
|
|||
| commands=$(java -cp /usr/share/java/leiningen-*-standalone.jar clojure.main -e "$1") | |||
There was a problem hiding this comment.
6. Jar path uses a glob. If leiningen-*-standalone.jar matches several files, the second one becomes the main-class argument. If it matches none, the literal pattern becomes the classpath. Either way -cp is wrong. The image already sets LEIN_VERSION, so /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar would be exact.
Generated by Claude Code
| (def ^:private lein-routing-check | ||
| "Checks every built-in command routes to lein, e.g. `test` -> `lein test`. | ||
| $1 is the `print-lein-commands` form." | ||
| (slurp (io/resource "lein-routing-check.sh"))) |
There was a problem hiding this comment.
7. Resource slurped at namespace load. If resources isn't on the classpath (or the file is renamed), io/resource returns nil and slurp throws while docker-clojure.docker loads. That breaks every entry point requiring the namespace, including generate/manifest commands that never smoke test. Reading it lazily inside smoke-test-cmds keeps the failure local.
Generated by Claude Code
| (vals cmds) | ||
| [(get cmds build-tool)]) | ||
| (and (= "lein" build-tool) (df/entrypoint? variant)) | ||
| (conj ["sh" "-c" lein-routing-check "sh" (pr-str print-lein-commands)])))) |
There was a problem hiding this comment.
8. No unit test for the new routing-check branch. A regression in this cond-> condition (e.g. the check stops being added for JDK 21 lein variants, or is wrongly added for ::core/all or JDK 11) or a syntax error in lein-routing-check.sh would pass clojure -X:test and only show up (or be silently missed) in CI image builds. A docker_test case asserting smoke-test-cmds output for lein/jdk-11, lein/jdk-21 and tools-deps would cover it.
Generated by Claude Code
|
|
||
| For JDK 17+ images: | ||
|
|
||
| ```sh | ||
| # Built-in Leiningen tasks and aliases | ||
| docker run --rm clojure:lein test |
There was a problem hiding this comment.
9. Docs vs. code. entrypoint? installs the wrapper for JDK 16+, but this says JDK 17+. Also, docker run --rm clojure:lein test and clojure:lein cp don't mount a project, so as written they fail with "Couldn't find project.clj". The custom-alias example below includes -v "$PWD":/app -w /app, and these probably should too.
Generated by Claude Code
| (testing "explicit commands and paths bypass shorthand routing" | ||
| (is (= ["lein" "my-alias"] (:calls (run-entrypoint "lein" "lein" "my-alias")))) | ||
| (is (= {:exit 0 :out "" :err "" :calls []} | ||
| (run-entrypoint "lein" "/bin/test" "1" "=" "1")))) |
There was a problem hiding this comment.
10. Tests depend on the host layout. The tests rely on /bin/test, /bin/sh, and #!/usr/bin/env bash resolving under a PATH restricted to /usr/bin:/bin. On hosts like NixOS/Guix, or where bash only lives in /usr/local/bin or /opt/homebrew/bin, every test fails with 127 instead of exercising the routing logic.
Generated by Claude Code
<cmd> --help, which could evaluate code from the current project; Lein images now route built-in tasks by a fixed list, and other commands run directly.clj; custom Leiningen tasks and aliases need an explicitlein(documented in the README).leincommand routes correctly (resources/lein-routing-check.sh), plus entrypoint unit tests.Fixes #301
🤖 Generated with Claude Code