Title: Generated IntelliJ run configuration has no resolvable module when a subproject name contains . (e.g. Stonecutter nodes) → run falls back to whole-project classpath
Environment
|
|
| ModDevGradle |
2.0.144 (same code on main as of today) |
| Gradle |
9.7.1 |
| IntelliJ IDEA |
2026.2 (IU-262.9437.185) |
| Multi-version tooling |
Stonecutter 0.9.7 |
| Project shape |
root project + version subprojects :1.21.1-neoforge (moddev) and :1.21.1-fabric (fabric-loom), sharing one src/ |
What happens
MDG generates the IntelliJ run configuration 1.21.1-neoforge - Client (Application type, net.neoforged.devlaunch.Main). In .idea/workspace.xml it ends up with no <module> element — the IDE shows "No module" in Edit Configurations.
Because the run has no module, IntelliJ builds -classpath from the whole project. In a multi-loader Stonecutter setup that pulls in the other node's Minecraft too, and NeoForge dev launch aborts before the window opens:
net.neoforged.fml.ModLoadingException: Loading errors encountered:
- Found multiple copies of net/minecraft/server/MinecraftServer.class on the classpath:
[.../versions/1.21.1-neoforge/build/moddev/artifacts/neoforge-21.1.228-merged.jar,
.../.gradle/loom-cache/minecraftMaven/net/minecraft/minecraft-merged-.../...-1.21.1-loom.mappings....jar]
at fml_loader/net.neoforged.fml.util.DevEnvUtils.findFileSystemRootOfFileOnClasspath(DevEnvUtils.java:78)
at fml_loader/net.neoforged.fml.loading.targets.CommonDevLaunchHandler.collectAdditionalModFileLocators(CommonDevLaunchHandler.java:59)
Running :1.21.1-neoforge:runClient as a Gradle task works fine — only the generated IDE Application config is affected.
Root cause
IntelliJIntegration.getIntellijModuleName:
moduleName.append(project.getRootProject().getName().replace(" ", "_")); // "xaeronav"
if (project != project.getRootProject()) {
moduleName.append(project.getPath().replaceAll(":", ".")); // ".1.21.1-neoforge"
}
moduleName.append(".").append(sourceSet.getName()); // ".main"
// => "xaeronav.1.21.1-neoforge.main"
The actual IntelliJ module for that subproject is xaeronav.1_21_1-neoforge.main — IntelliJ's Gradle import replaces . inside each path segment with _ (1.21.1-neoforge → 1_21_1-neoforge). MDG only maps : → . and does not sanitize the segments, so setModuleName(...) gets a name that doesn't exist, and the generated config ends up module-less.
The code comment already flags this:
// The actual IDEA logic is more complicated, but this should cover the majority of use cases.
Stonecutter node names are <mcVersion>-<loader>, so they always contain dots — every Stonecutter + MDG project hits this.
Why it's easy to miss / hard to recover
- Single-loader projects don't notice: "whole project classpath" == the only loader, so it just works.
disableIdeRun() stops MDG re-adding it, but MDG never removes the already-written config, and IntelliJ doesn't either — it has to be deleted from .idea/workspace.xml by hand once.
Suggested fix
Sanitize each segment of project.getPath() the way IntelliJ does (. → _, matching PathUtilRt / GradleProjectResolverUtil), or derive the module name from the idea model / ModuleRef instead of reconstructing the string.
Workaround (for anyone searching)
neoForge {
runs {
create("client") {
client()
disableIdeRun()
}
}
}
then delete the stale <node> - Client Application config from .idea/workspace.xml, and use a Gradle run config that runs :<node>:runClient.
Title: Generated IntelliJ run configuration has no resolvable module when a subproject name contains
.(e.g. Stonecutter nodes) → run falls back to whole-project classpathEnvironment
mainas of today):1.21.1-neoforge(moddev) and:1.21.1-fabric(fabric-loom), sharing onesrc/What happens
MDG generates the IntelliJ run configuration
1.21.1-neoforge - Client(Application type,net.neoforged.devlaunch.Main). In.idea/workspace.xmlit ends up with no<module>element — the IDE shows "No module" in Edit Configurations.Because the run has no module, IntelliJ builds
-classpathfrom the whole project. In a multi-loader Stonecutter setup that pulls in the other node's Minecraft too, and NeoForge dev launch aborts before the window opens:Running
:1.21.1-neoforge:runClientas a Gradle task works fine — only the generated IDE Application config is affected.Root cause
IntelliJIntegration.getIntellijModuleName:The actual IntelliJ module for that subproject is
xaeronav.1_21_1-neoforge.main— IntelliJ's Gradle import replaces.inside each path segment with_(1.21.1-neoforge→1_21_1-neoforge). MDG only maps:→.and does not sanitize the segments, sosetModuleName(...)gets a name that doesn't exist, and the generated config ends up module-less.The code comment already flags this:
// The actual IDEA logic is more complicated, but this should cover the majority of use cases.Stonecutter node names are
<mcVersion>-<loader>, so they always contain dots — every Stonecutter + MDG project hits this.Why it's easy to miss / hard to recover
disableIdeRun()stops MDG re-adding it, but MDG never removes the already-written config, and IntelliJ doesn't either — it has to be deleted from.idea/workspace.xmlby hand once.Suggested fix
Sanitize each segment of
project.getPath()the way IntelliJ does (.→_, matchingPathUtilRt/GradleProjectResolverUtil), or derive the module name from theideamodel /ModuleRefinstead of reconstructing the string.Workaround (for anyone searching)
neoForge { runs { create("client") { client() disableIdeRun() } } }then delete the stale
<node> - ClientApplication config from.idea/workspace.xml, and use a Gradle run config that runs:<node>:runClient.