termux/termux-app · critical · RuntimeException

Moving termux prefix staging to prefix directory failed

Error message

Moving termux prefix staging to prefix directory failed

What it means

Thrown when File.renameTo() from the staging prefix directory to the final prefix directory returns false. renameTo can fail across filesystems/mount points, when the destination already exists and is non-empty, or on permission/SELinux denial. The staged bootstrap is fully built but cannot be atomically moved into place.

Source

Thrown at app/src/main/java/com/termux/app/TermuxInstaller.java:216

                                        zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods")) {
                                        //noinspection OctalInteger
                                        Os.chmod(targetFile.getAbsolutePath(), 0700);
                                    }
                                }
                            }
                        }
                    }

                    if (symlinks.isEmpty())
                        throw new RuntimeException("No SYMLINKS.txt encountered");
                    for (Pair<String, String> symlink : symlinks) {
                        Os.symlink(symlink.first, symlink.second);
                    }

                    Logger.logInfo(LOG_TAG, "Moving termux prefix staging to prefix directory.");

                    if (!TERMUX_STAGING_PREFIX_DIR.renameTo(TERMUX_PREFIX_DIR)) {
                        throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
                    }

                    Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");

                    // Recreate env file since termux prefix was wiped earlier
                    TermuxShellEnvironment.writeEnvironmentToFile(activity);

                    activity.runOnUiThread(whenDone);

                } catch (final Exception e) {
                    showBootstrapErrorDialog(activity, whenDone, Logger.getStackTracesMarkdownString(null, Logger.getStackTracesStringArray(e)));

                } finally {
                    activity.runOnUiThread(() -> {
                        try {
                            progress.dismiss();
                        } catch (RuntimeException e) {
                            // Activity already dismissed - ignore.

View on GitHub (pinned to 3df69d1da1)

Solutions

  1. Delete any pre-existing TERMUX_PREFIX_DIR before the rename so the destination path does not exist.
  2. Ensure both staging and prefix paths share the same filesystem (both under the app's private files dir).
  3. Replace renameTo with a copy-then-delete fallback when renameTo returns false, since cross-device rename is not atomic.
  4. Check logcat for SELinux denials or permission errors around the rename call.

Example fix

// before
if (!TERMUX_STAGING_PREFIX_DIR.renameTo(TERMUX_PREFIX_DIR)) {
    throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
}

// after (fallback copy when rename fails, e.g. cross-device)
if (!TERMUX_STAGING_PREFIX_DIR.renameTo(TERMUX_PREFIX_DIR)) {
    try {
        org.apache.commons.io.FileUtils.moveDirectory(TERMUX_STAGING_PREFIX_DIR, TERMUX_PREFIX_DIR);
    } catch (IOException moveEx) {
        throw new RuntimeException("Moving termux prefix staging to prefix directory failed", moveEx);
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight: ensure destination does not exist and both paths share a filesystem
if (TERMUX_PREFIX_DIR.exists()) {
    deleteRecursively(TERMUX_PREFIX_DIR);
}
boolean sameFs = TERMUX_STAGING_PREFIX_DIR.getParent().equals(TERMUX_PREFIX_DIR.getParent());

Try / catch

try {
    if (!TERMUX_STAGING_PREFIX_DIR.renameTo(TERMUX_PREFIX_DIR)) {
        copyDirectory(TERMUX_STAGING_PREFIX_DIR, TERMUX_PREFIX_DIR);
        deleteRecursively(TERMUX_STAGING_PREFIX_DIR);
    }
} catch (IOException moveEx) {
    showBootstrapErrorDialog(activity, whenDone, "Failed to move prefix: " + moveEx.getMessage());
}

Prevention

When it happens

Trigger: TERMUX_STAGING_PREFIX_DIR and TERMUX_PREFIX_DIR live on different filesystems so renameTo cannot do a cross-device move; the destination prefix dir already exists with leftover files; a parent directory lacks write permission; SELinux policy denies the rename; another process holds files open in the destination.

Common situations: A previous failed install left a partial TERMUX_PREFIX_DIR behind; device-specific filesystem layout where staging and prefix resolve to different mounts; storage permission revoked; antivirus or another app locked files under the prefix.

Related errors


AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13). Data as JSON: /api/errors/4fcbb2097afbe35f. Report an issue: GitHub.