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

  1. 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.).
  2. Re-download or re-create the theme ZIP and verify it opens locally (unzip -t theme.zip).
  3. Check the theme work directory is writable and has disk space.
  4. 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

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


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/65ad58c9590ddd15. Report an issue: GitHub.