jdx/mise · error

cannot retain an unreadable plaintext version of newly encry

Error message

cannot retain an unreadable plaintext version of newly encrypted {}

What it means

During checkpoint preimage retention (`retain_omitted`), mise refuses to keep an omitted file as a plaintext copy when its policy says the file must be encrypted and the stored blob is not in the `mise-encrypted-file-v1` format. Keeping a readable plaintext copy of a newly encrypted file would defeat the encryption policy, so the checkpoint aborts.

Source

Thrown at src/system/history/checkpoint.rs:903

        .flat_map(|path| path.ancestors().skip(1))
        .collect();
    for file in repo.ls_tree(parent)? {
        let located = roots.locate(&file.path);
        let Some(path) = located.path() else { continue };
        if !omitted.iter().any(|omitted| path.starts_with(omitted))
            || !tracked.would_retain(path)?
            || repo.object_at(tree, &file.path)?.is_some()
        {
            continue;
        }
        let Some(entry) = tracked.entry_for(path) else {
            continue;
        };
        if entry.tree_path(path)? != file.path {
            continue;
        }
        if entry.policy.encrypt && !repo.blob_starts_with(&file.oid, b"mise-encrypted-file-v1\n")? {
            eyre::bail!(
                "cannot retain an unreadable plaintext version of newly encrypted {}",
                display_path(path)
            );
        }
        let display = display_path(path);
        modes.remove(&display);
        if let Some(mode) = record.tree.modes.get(&display) {
            modes.insert(display, *mode);
        }
        for parent in path
            .ancestors()
            .skip(1)
            .take_while(|parent| parent.starts_with(&entry.path))
        {
            if !observed_directories.contains(parent) {
                let display = display_path(parent);
                if let Some(mode) = record.tree.modes.get(&display) {
                    modes.insert(display, *mode);

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Re-initialize or re-snapshot the checkpoint store so the file is captured in encrypted form under the new policy.
  2. Remove the file from history tracking (or its preimage entry) so no plaintext preimage is retained, then re-add it after encryption.
  3. Temporarily disable encryption policy for the path if plaintext retention is intended, then re-enable after migrating.

Example fix

// before: enable encryption over an already-tracked plaintext file
[[history.encryption.files]]
path = "secrets.env"
encrypt = true
// after: purge the plaintext preimage first, then re-track
mise history checkpoint --purge secrets.env  # or equivalent removal, then re-encrypt & re-snapshot
Defensive patterns

Strategy: try-catch

Validate before calling

let head = repo.blob_starts_with(&file.oid, b"mise-encrypted-file-v1\n")?;
if entry.policy.encrypt && !head { /* migrate/re-encrypt before checkpoint */ }

Try / catch

match result {
    Err(e) if e.to_string().contains("unreadable plaintext version") => migrate_to_encrypted(path)?,
    other => other?,
}

Prevention

When it happens

Trigger: A path's checkpoint policy has `policy.encrypt = true`, the file's oid blob in the repo does NOT start with `mise-encrypted-file-v1\n`, and the path is being omitted from the current checkpoint while retention tries to preserve it.

Common situations: A user recently added `[history.encryption]` recipients for a config file that was previously tracked in plaintext; the old plaintext blob still exists in the checkpoint store and the retention step detects the mismatch.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/7ee9d477af3145b5. Report an issue: GitHub.