HMCL-dev/HMCL · error · IOException
Theme-pack file is outside the managed directory:
Error message
Theme-pack file is outside the managed directory:
What it means
uninstall() validates that the pack's absolute normalized file lives under THEME_PACKS_DIRECTORY or USER_THEME_PACKS_DIRECTORY. If it resides anywhere else, the manager refuses to delete it and throws this IOException with the offending path, as a safety measure against removing arbitrary user files.
Solutions
- Install the pack first with ThemePackManager.install(Path) so it is copied into a managed directory, then uninstall that installed copy.
- If deleting an unmanaged pack, delete the file yourself (Files.deleteIfExists) instead of going through uninstall().
- Check that THEME_PACKS_DIRECTORY/USER_THEME_PACKS_DIRECTORY are where you expect (they may change between HMCL versions or portable-mode configs).
Example fix
// before ThemePackManager.uninstall(ThemePackManager.loadInstalled(downloadedZip)); // after InstalledThemePack installed = ThemePackManager.install(downloadedZip); ThemePackManager.uninstall(installed);
Defensive patterns
Strategy: validation
Validate before calling
Path target = pack.file().toAbsolutePath().normalize();
Path local = ThemePackManager.THEME_PACKS_DIRECTORY.toAbsolutePath().normalize();
Path user = ThemePackManager.USER_THEME_PACKS_DIRECTORY.toAbsolutePath().normalize();
boolean managed = (target.startsWith(local) && !target.equals(local))
|| (target.startsWith(user) && !target.equals(user));
if (!managed) {
// install the pack into a managed directory first, then uninstall that copy
InstalledThemePack inst = ThemePackManager.install(pack.file());
} Try / catch
try {
ThemePackManager.uninstall(pack);
} catch (IOException e) {
if (e.getMessage().startsWith("Theme-pack file is outside the managed directory")) {
// fall back to deleting the file directly or install-then-uninstall
}
} Prevention
- Always install packs into the managed directories before managing their lifecycle
- Load packs from ad-hoc paths read-only; never call uninstall on them
- Check where THEME_PACKS_DIRECTORY points (portable mode can relocate it)
When it happens
Trigger: Calling uninstall() on an InstalledThemePack whose file path points outside both managed theme-pack directories — e.g. a pack in the user's home or Downloads folder that was loaded via load() without ever being installed.
Common situations: Double-clicking a theme pack downloaded from the web and attempting to uninstall it directly; loading packs from a custom directory and then calling uninstall on them; symlinked or relocated install directories making the normalized path differ from expectations.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Theme-pack asset entry must be relative:
- Theme-pack asset entry must be under assets/:
- Theme-pack asset entry must be a file:
- Cannot delete a built-in theme pack:
- Theme pack does not have a local file:
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/27788d673bceec19.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:552
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
? null
: replacementThemePack.manifest().findTheme(reference.themeId());
if (replacementTheme == null) {
settings().selectedThemeProperty().set(BUILTIN_DEFAULT_THEME_REFERENCE);
}
}
}
/// Applies one theme from a loaded theme pack to current launcher settings.View on GitHub (pinned to 24702dc5a0)