MuntashirAkon/AppManager · error · IOException

Backup is in read-only mode.

Error message

Backup is in read-only mode.

What it means

writeMetadata refuses to write when the BackupItem is not in backup (writable) mode, throwing IOException "Backup is in read-only mode." This protects restored/loaded backup views from being mutated accidentally.

Solutions

  1. Ensure the BackupItem is created/opened in backup mode (set the backup mode flag before writing).
  2. Write metadata only during an active backup/creation session, not when viewing or restoring an existing backup.
  3. If you must edit an existing backup, re-create it via a new backup operation instead of mutating read-only state.

Example fix

// before
MetadataManager.writeMetadata(metadata, backupItem);
// after
if (!backupItem.isBackupMode()) {
    throw new IllegalStateException("Cannot write metadata: backup opened in read-only mode");
}
MetadataManager.writeMetadata(metadata, backupItem);
Defensive patterns

Strategy: type-guard

Type guard

boolean canWriteMetadata(BackupItems.BackupItem item) { return item.isBackupMode(); }

Try / catch

try {
    MetadataManager.writeMetadata(metadata, backupFile);
} catch (IOException e) {
    if ("Backup is in read-only mode.".equals(e.getMessage())) {
        // reopen the BackupItem in backup mode or inform the user
    } else throw e;
}

Prevention

When it happens

Trigger: Calling writeMetadata with a BackupItem opened in restore/view mode (isBackupMode() == false), e.g. after loading an existing backup for reading rather than creating one during a backup operation.

Common situations: Trying to update metadata of an existing backup from a read-only viewer context, or reusing a BackupItem constructed for restore inside a metadata-editing flow.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/6c0d69fb582b0366. Report an issue: GitHub.

Appendix: source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/backup/MetadataManager.java:107

        }
        try {
            jsonObject = new JSONObject(metadataString);
            if (!v5AndUp) {
                // Meta is a subset of meta_v2.am.json except for backup_name
                jsonObject.put("backup_name", backupItem.getBackupName());
            }
            BackupMetadataV5.Metadata metadata = new BackupMetadataV5.Metadata(jsonObject);
            return new BackupMetadataV5(backupInfo, metadata);
        } catch (JSONException e) {
            throw new IOException(e.getMessage() + " for path " + metadataFile, e);
        }
    }

    @WorkerThread
    @NonNull
    public static Map<String, String> writeMetadata(@NonNull BackupMetadataV5 metadata, @NonNull BackupItems.BackupItem backupFile) throws IOException {
        if (!backupFile.isBackupMode()) {
            throw new IOException("Backup is in read-only mode.");
        }
        if (metadata.info.version >= 5) {
            // v5 and up
            return writeMetadataV5(metadata, backupFile);
        } else {
            // Old style backup
            return writeMetadataV2(metadata, backupFile);
        }
    }

    @WorkerThread
    @NonNull
    private static Map<String, String> writeMetadataV2(@NonNull BackupMetadataV5 metadata, @NonNull BackupItems.BackupItem backupFile) throws IOException {
        Path metadataFile = backupFile.getMetadataV2File();
        try (OutputStream outputStream = metadataFile.openOutputStream()) {
            JSONObject metadataObject = metadata.info.serializeToJson();
            JSONUtils.putAll(metadataObject, metadata.metadata.serializeToJson());
            // Info is a subset of meta_v2.am.json except for backup_name

View on GitHub (pinned to 0152f468fc)