{"record":{"id":"99c8cd74e0943302","repo":"HMCL-dev/HMCL","slug":"file-missing","errorCode":null,"errorMessage":"File missing: ","messagePattern":"File missing: ","errorType":"exception","errorClass":"FileNotFoundException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java","lineNumber":158,"sourceCode":"            List<String> args = new ArrayList<>(processor.getArgs().size());\n            for (String arg : processor.getArgs()) {\n                String parsed = parseLiteral(arg, vars);\n                if (parsed == null)\n                    throw new ArtifactMalformedException(\"Invalid forge installation configuration\");\n                args.add(parsed);\n            }\n\n            command.addAll(args);\n\n            LOG.info(\"Executing external processor \" + processor.getJar().toString() + \", command line: \" + new CommandBuilder().addAll(command).toString());\n            int exitCode = SystemUtils.callExternalProcess(command);\n            if (exitCode != 0)\n                throw new IOException(\"Game processor exited abnormally with code \" + exitCode);\n\n            for (Map.Entry<String, String> entry : outputs.entrySet()) {\n                Path artifact = Paths.get(entry.getKey());\n                if (!Files.isRegularFile(artifact))\n                    throw new FileNotFoundException(\"File missing: \" + artifact);\n\n                String code;\n                try (InputStream stream = Files.newInputStream(artifact)) {\n                    code = DigestUtils.digestToString(\"SHA-1\", stream);\n                }\n\n                if (!Objects.equals(code, entry.getValue())) {\n                    if (!ZlibUtils.IS_ZLIB_COMPATIBLE && FileUtils.getExtension(artifact).equals(\"jar\")) {\n                        // Forge/NeoForge generates JARs dynamically during installation.\n                        // When native compression libraries such as zlib-ng are in use,\n                        // the resulting JAR may be compressed differently, causing its\n                        // SHA-1 hash to differ from the expected value recorded in the\n                        // install profile. In this case, fall back to verifying that the\n                        // file is at least a structurally valid ZIP/JAR archive.\n                        try {\n                            FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(artifact, artifact);\n                            LOG.info(\"Ignoring SHA-1 mismatch for \" + artifact + \" due to non-standard zlib compression output\");\n                            continue;","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java#L140-L176","documentation":"Thrown when a Forge processor reports success but its declared output file is not present on disk. After each processor runs, HMCL verifies every entry in the outputs map exists; a missing file means the processor silently failed to produce a required artifact (e.g. patched jar or binpatches).","triggerScenarios":"Following a successful processor execution (exit code 0), Paths.get(outputKey) is not a regular file — the processor wrote nothing or wrote to a different path because tokens (e.g. {MINECRAFT_JAR}, {ROOT}) resolved to unexpected locations.","commonSituations":"Processor partially ran and crashed after process exit code was misreported; permission issues preventing writes into the game directory; token mismatch causing output to a different directory; stale antivirus interference.","solutions":["Check game-directory write permissions so the processor can create output files","Verify token substitution (ROOT/MINECRAFT_JAR etc.) resolved to the expected paths in HMCL logs","Delete stale processor outputs and re-run installation so processors execute cleanly","Inspect processor logs to confirm which output it was supposed to produce and why it did not"],"exampleFix":"// before: running on a read-only game directory\nPath base = Paths.get(\"/mnt/ro/minecraft\");\n// after: ensure the repository is writable\nif (!Files.isWritable(base)) throw new IOException(\"Game directory must be writable: \" + base);\nnew ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, version, installer);","handlingStrategy":"validation","validationCode":"// Ensure the game directory is writable so processors can emit outputs\nPath base = gameRepository.getBaseDirectory();\nif (!Files.isWritable(base)) throw new IOException(\"Game directory not writable: \" + base);","typeGuard":null,"tryCatchPattern":"try {\n    installTask.run();\n} catch (FileNotFoundException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"File missing:\")) {\n        // check permissions/token substitution; clean temp outputs and retry install\n    }\n}","preventionTips":["Install into a writable game directory (avoid read-only mounts)","Do not run two installations into the same directory concurrently","Clear stale temp/processor outputs before retrying"],"tags":["forge","file-not-found","processor-output"],"backgroundTag":"file-not-found","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}