HMCL-dev/HMCL · error · IOException

Theme pack does not have a local file:

Error message

Theme pack does not have a local file: 

What it means

uninstall() throws this IOException when the pack is not built-in but its file() is null, i.e. the InstalledThemePack records a manifest id but no backing local path. Such a pack exists only logically (e.g. an in-memory or resource-only descriptor) and cannot be removed from disk.

Solutions

  1. Verify themePack.file() != null before calling uninstall and treat null-file packs as already removed.
  2. Reinstall the theme pack via ThemePackManager.install(Path) so it has a backing file, then uninstall it.
  3. Prune pack records whose file() is null from your pack registry instead of attempting deletion.

Example fix

// before
ThemePackManager.uninstall(themePack);
// after
if (themePack.file() != null) {
    ThemePackManager.uninstall(themePack);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (pack.builtin()) return;
if (pack.file() == null) { /* treat as already removed / reinstall first */ }

Type guard

static boolean hasLocalFile(InstalledThemePack p) {
    return !p.builtin() && p.file() != null && Files.isRegularFile(p.file());
}

Try / catch

try {
    ThemePackManager.uninstall(pack);
} catch (IOException e) {
    if (e.getMessage().startsWith("Theme pack does not have a local file")) {
    // prune the stale record instead of retrying deletion
    }
}

Prevention

When it happens

Trigger: Calling uninstall() on a non-builtin InstalledThemePack constructed with a null file path — for example a pack descriptor created without a local install location, or one whose location was cleared.

Common situations: Constructing InstalledThemePack records manually with null paths in tests or custom tooling; deserializing pack metadata from config without restoring the file path; a stale cache entry whose file was already deleted and nulled.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/851c1560d8acc0c5. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:543

        }
    }

    /// Removes an installed theme pack from a managed theme-pack directory.
    ///
    /// If the removed package is currently selected, the stored theme reference is cleared.
    ///
    /// @param themePack the installed theme pack to remove
    /// @throws IOException if the installed package cannot be removed
    public static void uninstall(InstalledThemePack themePack) throws IOException {
        Objects.requireNonNull(themePack);

        if (themePack.builtin()) {
            throw new IOException("Cannot delete a built-in theme pack: " + themePack.manifest().id());
        }

        @Nullable Path file = themePack.file();
        if (file == null) {
            throw new IOException("Theme pack does not have a local file: " + themePack.manifest().id());
        }

        Path targetFile = file.toAbsolutePath().normalize();
        Path localDirectory = THEME_PACKS_DIRECTORY.toAbsolutePath().normalize();
        Path userDirectory = USER_THEME_PACKS_DIRECTORY.toAbsolutePath().normalize();
        boolean localThemePack = targetFile.startsWith(localDirectory) && !targetFile.equals(localDirectory);
        boolean userThemePack = targetFile.startsWith(userDirectory) && !targetFile.equals(userDirectory);
        if (!localThemePack && !userThemePack) {
            throw new IOException("Theme-pack file is outside the managed directory: " + targetFile);
        }

        deleteIfExists(targetFile);

        ThemeReference reference = settings().getSelectedThemeOrDefault();
        ThemePackManifest manifest = themePack.manifest();
        if (reference.packId().equals(manifest.id())) {
            @Nullable InstalledThemePack replacementThemePack = findInstalled(reference);
            @Nullable Theme replacementTheme = replacementThemePack == null

View on GitHub (pinned to 24702dc5a0)