Version(s)
- Scala CLI 1.14.0, default Scala 3.8.3
- Bloop v2.0.19 (Zinc v1.12.0), daemon on GraalVM CE JDK 24
- Project pins
//> using jvm 21
- macOS 26.5.1 (arm64)
Summary
Compiling a Java-only scala-cli project (no .scala sources, no //> using scala)
whose dependency classes are Scala 3 libraries can hang scala-cli compile forever
(we observed a CI job sit 3h37m before being killed; locally it never returns).
javac itself succeeds — running plain javac with the same classpath compiles the
file in ~0.5s.
- The hang happens after javac, inside Zinc's post-compile Java analysis in the Bloop
server: JavaAnalyze/ClassToAPI reflectively loads classes referenced by the produced
classfiles, and dies with NoClassDefFoundError: scala/reflect/Enum — the analysis
classloader can't see scala3-library even though it is on the project's compile
classpath (it's a transitive dependency of the library being used).
- The failed compile task then never answers the BSP request: the scala-cli client
blocks in the bloop-rifle connection with 0% CPU, printing nothing after
Compiling project (Java). There is no error, no timeout — just an eternal hang.
The failure is only visible in the Bloop daemon log
(~/Library/Caches/ScalaCli/bloop/daemon/output).
So there appear to be two stacked defects:
- Zinc/Bloop: the Java-analysis classloader used by
AnalyzingJavaCompiler/
JavaAnalyze is missing the Scala library for Java-only projects, so analyzing any
classfile that references a Scala 3 enum (they implement scala.reflect.Enum)
throws.
- Bloop (the severe one): an exception in the compile pipeline results in a
never-completed BSP request instead of a failed compile — turning an analysis bug
into an indefinite client hang with no diagnostics.
Reproduction (verified against public Maven Central artifacts)
Repro.java:
//> using dep "io.github.riccardomerolla:llm4zio-java:3.12.0"
//> using jvm 21
import llm4zio.javaapi.*;
import llm4zio.flow.Plan;
public class Repro {
public static void main(String[] args) {
Llm4zioJava.flow(args, "owner/repo#number", flow -> {
var maybeRef = Refs.issue(flow.userPrompt());
if (maybeRef.isEmpty()) {
flow.fail("usage");
return;
}
var ref = maybeRef.get();
var issue = flow.stage("Read issue " + ref.shortRef(), () -> flow.gh().readIssue(ref));
var payload = "Issue: " + issue.title();
var planPath = flow.workDir().resolve(".llm4zio/issue-" + ref.number() + ".md");
Plan plan;
var existing = flow.loadPlan(planPath);
if (existing.isPresent()) {
plan = existing.get();
} else {
var assessment = flow.assessThenPlan(payload);
if (assessment instanceof JavaAssessment.Blocked blocked) {
flow.stage("Post assessment on the issue", () -> flow.gh().writeIssueComment(ref, blocked.reason()));
return;
}
plan = ((JavaAssessment.Proceed) assessment).plan();
flow.git().checkoutOrCreate(plan.epicId());
flow.savePlan(planPath, plan);
}
System.out.println(plan.epicId());
});
}
}
scala-cli compile Repro.java # prints "Compiling project (Java)" and never returns
(llm4zio-java is an ordinary Scala 3 library published for Java consumers;
JavaAssessment is a Scala 3 enum with payload cases, Plan a case class, etc.)
Expected
Either a successful compile (javac succeeds; analysis should too — scala3-library_3
is on the compile classpath), or at minimum a failed compile with the
NoClassDefFoundError surfaced to the client. Never a silent, indefinite hang.
Actual
Client output stops after:
and the process waits forever (0% CPU, blocked in the bloop-rifle client; jstack of
the Bloop server shows no compile thread running). The Bloop daemon log contains:
java.lang.NoClassDefFoundError: scala/reflect/Enum
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:962)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:144)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3010)
at java.base/java.lang.Class.getDeclaredMethods(Class.java:2329)
at sbt.internal.inc.ClassToAPI$.structure(ClassToAPI.scala:196)
...
at sbt.internal.inc.classfile.JavaAnalyze$.apply(JavaAnalyze.scala:108)
at sbt.internal.inc.javac.AnalyzingJavaCompiler.$anonfun$compile$22(AnalyzingJavaCompiler.scala:216)
at sbt.internal.inc.javac.AnalyzingJavaCompiler.compile(AnalyzingJavaCompiler.scala:208)
at sbt.internal.inc.bloop.internal.BloopHighLevelCompiler.$anonfun$compile$11(BloopHighLevelCompiler.scala:197)
...
Caused by: java.lang.ClassNotFoundException: scala.reflect.Enum
Control experiments
| Variant |
Result |
Same file + classpath, plain javac |
compiles in ~0.5s |
Same file + //> using scala "3.8.3" (mixed project) |
compiles fine |
Same file + --server=false (no Bloop) |
compiles fine |
Trivial repro attempt: one-enum Scala 3 lib (enum Status { case Ok; case Bad(reason: String) }) + a Java file using valueOf/instanceof on it |
does not reproduce — compiles fine |
The last row is the minimization frontier we reached: a single tiny Scala 3 dependency
doesn't trigger it, while the (larger) real dependency graph above does, deterministically
— same file hangs across fresh project dirs and Bloop daemon restarts. Whatever
ClassToAPI ends up loading in the failing case pulls in a type whose loader is missing
scala3-library.
Workarounds (for anyone else hitting this)
- Add
//> using scala "<your scala version>" to the .java file — the project becomes
mixed and analysis has the Scala library. (This is what we ship in our examples now.)
- Or run with
--power --server=false.
Impact
Any Java-first consumer of a Scala 3 library via scala-cli (single-file Java "scripts"
against a Scala library) can hit this, and the failure mode is the worst kind: an
indefinite silent hang, locally and in CI.
Filing here since scala-cli compile is where users hit it, but the root cause looks split between Bloop and Zinc — happy for this to be moved/split as appropriate.
Version(s)
//> using jvm 21Summary
Compiling a Java-only scala-cli project (no
.scalasources, no//> using scala)whose dependency classes are Scala 3 libraries can hang
scala-cli compileforever(we observed a CI job sit 3h37m before being killed; locally it never returns).
javacitself succeeds — running plainjavacwith the same classpath compiles thefile in ~0.5s.
server:
JavaAnalyze/ClassToAPIreflectively loads classes referenced by the producedclassfiles, and dies with
NoClassDefFoundError: scala/reflect/Enum— the analysisclassloader can't see
scala3-libraryeven though it is on the project's compileclasspath (it's a transitive dependency of the library being used).
blocks in the bloop-rifle connection with 0% CPU, printing nothing after
Compiling project (Java). There is no error, no timeout — just an eternal hang.The failure is only visible in the Bloop daemon log
(
~/Library/Caches/ScalaCli/bloop/daemon/output).So there appear to be two stacked defects:
AnalyzingJavaCompiler/JavaAnalyzeis missing the Scala library for Java-only projects, so analyzing anyclassfile that references a Scala 3
enum(they implementscala.reflect.Enum)throws.
never-completed BSP request instead of a failed compile — turning an analysis bug
into an indefinite client hang with no diagnostics.
Reproduction (verified against public Maven Central artifacts)
Repro.java:scala-cli compile Repro.java # prints "Compiling project (Java)" and never returns(
llm4zio-javais an ordinary Scala 3 library published for Java consumers;JavaAssessmentis a Scala 3 enum with payload cases,Plana case class, etc.)Expected
Either a successful compile (javac succeeds; analysis should too —
scala3-library_3is on the compile classpath), or at minimum a failed compile with the
NoClassDefFoundErrorsurfaced to the client. Never a silent, indefinite hang.Actual
Client output stops after:
and the process waits forever (0% CPU, blocked in the bloop-rifle client;
jstackofthe Bloop server shows no compile thread running). The Bloop daemon log contains:
Control experiments
javac//> using scala "3.8.3"(mixed project)--server=false(no Bloop)enum Status { case Ok; case Bad(reason: String) }) + a Java file usingvalueOf/instanceofon itThe last row is the minimization frontier we reached: a single tiny Scala 3 dependency
doesn't trigger it, while the (larger) real dependency graph above does, deterministically
— same file hangs across fresh project dirs and Bloop daemon restarts. Whatever
ClassToAPIends up loading in the failing case pulls in a type whose loader is missingscala3-library.Workarounds (for anyone else hitting this)
//> using scala "<your scala version>"to the.javafile — the project becomesmixed and analysis has the Scala library. (This is what we ship in our examples now.)
--power --server=false.Impact
Any Java-first consumer of a Scala 3 library via scala-cli (single-file Java "scripts"
against a Scala library) can hit this, and the failure mode is the worst kind: an
indefinite silent hang, locally and in CI.
Filing here since
scala-cli compileis where users hit it, but the root cause looks split between Bloop and Zinc — happy for this to be moved/split as appropriate.