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
- Verify themePack.file() != null before calling uninstall and treat null-file packs as already removed.
- Reinstall the theme pack via ThemePackManager.install(Path) so it has a backing file, then uninstall it.
- 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
- Never construct InstalledThemePack with a null path for uninstall flows
- Reinstall via install(Path) if the backing file was lost
- Prune pack records with null file() from your registry during startup
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
- Cannot delete a built-in theme pack:
- Theme-pack file is outside the managed directory:
- Theme pack location has no readable file:
- Theme-pack asset entry is empty
- Theme-pack asset entry must be relative:
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 == nullView on GitHub (pinned to 24702dc5a0)