halo-dev/halo · error · ServerWebInputException
Failed to unzip theme
Error message
Failed to unzip theme
What it means
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.
Source
Thrown at application/src/main/java/run/halo/app/theme/service/ThemeUtils.java:108
return true;
})
.map(FileSystemResource::new)
.toList();
return new YamlUnstructuredLoader(resources.toArray(new Resource[0])).load();
} catch (IOException e) {
if (e instanceof NoSuchFileException) {
return List.of();
}
throw new RuntimeException(e);
}
}
static Mono<Unstructured> unzipThemeTo(
Publisher<DataBuffer> content, Path themeWorkDir, @Nullable Scheduler scheduler) {
return unzipThemeTo(content, themeWorkDir, false, scheduler)
.onErrorMap(e -> !(e instanceof ResponseStatusException), e -> {
log.error("Failed to unzip theme", e);
throw new ServerWebInputException("Failed to unzip theme");
});
}
static Mono<Unstructured> unzipThemeTo(
Publisher<DataBuffer> content, Path themeWorkDir, boolean override, @Nullable Scheduler scheduler) {
var unzipThem = Mono.usingWhen(
createTempDir(THEME_TMP_PREFIX, null),
tempDir -> {
var locateThemeManifest = Mono.fromCallable(
() -> locateThemeManifest(tempDir).orElse(null))
.switchIfEmpty(Mono.error(() -> new ThemeInstallationException(
"Missing theme manifest", "problemDetail.theme.install.missingManifest", null)));
return unzip(content, tempDir, null)
.then(locateThemeManifest)
.<Unstructured>handle((themeManifestPath, sink) -> {
var theme = loadThemeManifest(themeManifestPath);
var themeName = theme.getMetadata().getName();
var themeTargetPath = themeWorkDir.resolve(themeName);View on GitHub (pinned to d2f5165f9c)
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.
Example fix
# verify the zip before uploading # unzip -t theme.zip && echo OK # if it fails, repackage: # zip -r theme.zip theme/
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the archive is a valid zip before uploading (Java):
try (ZipFile zf = new ZipFile(file)) {
// opened successfully
} catch (IOException e) {
throw new IllegalArgumentException("Not a valid zip: " + e.getMessage(), e);
}
// Shell: unzip -t theme.zip Try / catch
try {
themeService.install(content).block();
} catch (ServerWebInputException e) {
if ("Failed to unzip theme".equals(e.getReason())) {
// consult server log for root cause; the original exception is logged at ERROR
log.error("Unzip failed; see earlier ERROR log for the cause");
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid multipart type of file
- Only zip extension supported
- Invalid parameter of file, binary data is required
- Invalid file type, only zip format is supported
- problemDetail.theme.version.unsatisfied.requires
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/65ad58c9590ddd15.
Report an issue: GitHub.