{"record":{"id":"65ad58c9590ddd15","repo":"halo-dev/halo","slug":"failed-to-unzip-theme","errorCode":null,"errorMessage":"Failed to unzip theme","messagePattern":"Failed to unzip theme","errorType":"http","errorClass":"ServerWebInputException","httpStatus":400,"severity":"error","filePath":"application/src/main/java/run/halo/app/theme/service/ThemeUtils.java","lineNumber":108,"sourceCode":"                        return true;\n                    })\n                    .map(FileSystemResource::new)\n                    .toList();\n            return new YamlUnstructuredLoader(resources.toArray(new Resource[0])).load();\n        } catch (IOException e) {\n            if (e instanceof NoSuchFileException) {\n                return List.of();\n            }\n            throw new RuntimeException(e);\n        }\n    }\n\n    static Mono<Unstructured> unzipThemeTo(\n            Publisher<DataBuffer> content, Path themeWorkDir, @Nullable Scheduler scheduler) {\n        return unzipThemeTo(content, themeWorkDir, false, scheduler)\n                .onErrorMap(e -> !(e instanceof ResponseStatusException), e -> {\n                    log.error(\"Failed to unzip theme\", e);\n                    throw new ServerWebInputException(\"Failed to unzip theme\");\n                });\n    }\n\n    static Mono<Unstructured> unzipThemeTo(\n            Publisher<DataBuffer> content, Path themeWorkDir, boolean override, @Nullable Scheduler scheduler) {\n        var unzipThem = Mono.usingWhen(\n                createTempDir(THEME_TMP_PREFIX, null),\n                tempDir -> {\n                    var locateThemeManifest = Mono.fromCallable(\n                                    () -> locateThemeManifest(tempDir).orElse(null))\n                            .switchIfEmpty(Mono.error(() -> new ThemeInstallationException(\n                                    \"Missing theme manifest\", \"problemDetail.theme.install.missingManifest\", null)));\n                    return unzip(content, tempDir, null)\n                            .then(locateThemeManifest)\n                            .<Unstructured>handle((themeManifestPath, sink) -> {\n                                var theme = loadThemeManifest(themeManifestPath);\n                                var themeName = theme.getMetadata().getName();\n                                var themeTargetPath = themeWorkDir.resolve(themeName);","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/theme/service/ThemeUtils.java#L90-L126","documentation":"The 3-argument unzipThemeTo() wrapper maps any error that is NOT already a ResponseStatusException to a ServerWebInputException (HTTP 400) 'Failed to unzip theme', after logging the original cause at ERROR. It is a catch-all over the real unzip pipeline (Mono.usingWhen: temp dir, extract, locate manifest, copy). So the user-visible message is generic but the server log contains the root cause.","triggerScenarios":"Uploading a theme ZIP that is corrupt, truncated, empty, password-protected, or not actually a zip; an I/O error writing to the theme work directory; a permission error on the temp/theme dir; the archive references a path that cannot be created. Any such failure during install/upgrade surfaces as this 400.","commonSituations":"Interrupted download producing a truncated zip; uploading a .zip that is actually another format; disk full or read-only theme directory; OS extracting a zip with unsupported features.","solutions":["Read the server log: the original exception is logged at ERROR just before this is thrown, and names the real cause (corrupt zip, IOException, etc.).","Re-download or re-create the theme ZIP and verify it opens locally (unzip -t theme.zip).","Check the theme work directory is writable and has disk space.","Ensure the archive is an uncompressed/deflated zip, not encrypted or a different format."],"exampleFix":"# verify the zip before uploading\n#   unzip -t theme.zip && echo OK\n# if it fails, repackage:\n#   zip -r theme.zip theme/","handlingStrategy":"try-catch","validationCode":"// Verify the archive is a valid zip before uploading (Java):\ntry (ZipFile zf = new ZipFile(file)) {\n    // opened successfully\n} catch (IOException e) {\n    throw new IllegalArgumentException(\"Not a valid zip: \" + e.getMessage(), e);\n}\n// Shell: unzip -t theme.zip","typeGuard":null,"tryCatchPattern":"try {\n    themeService.install(content).block();\n} catch (ServerWebInputException e) {\n    if (\"Failed to unzip theme\".equals(e.getReason())) {\n        // consult server log for root cause; the original exception is logged at ERROR\n        log.error(\"Unzip failed; see earlier ERROR log for the cause\");\n    }\n    throw e;\n}","preventionTips":["Always validate archives locally (unzip -t) before uploading.","Ensure the theme work directory is writable with adequate disk space.","Keep the original cause in server logs when wrapping to a generic 400 (already done here).","Avoid encrypted/special zip features; use standard deflated entries."],"tags":["theme","install","unzip","upload","io"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}