jdx/mise · error

cannot retain an unreadable plaintext version of newly…

Error message

cannot retain an unreadable plaintext version of newly encrypted {}

What it means

When a checkpoint omits certain files but tries to retain them in a readable form, retain_omitted validates each omitted entry against the recorded policy. If the policy marks the file as encrypted but the stored blob is not actually in the mise-encrypted-file-v1 format, keeping a plaintext copy of a newly encrypted file would defeat the encryption, so it bails. This is a safety invariant: encryption must be retroactive for the file, never leave readable plaintext.

Solutions

  1. Re-capture or re-checkpoint the file so it is stored in the encrypted format under the current policy
  2. Verify history.encryption recipients are configured and the encryption layer actually rewrote the file's blob (check it starts with mise-encrypted-file-v1)
  3. Exclude the path from the checkpoint omission list until its encrypted version exists, or temporarily remove the file from encryption policy to complete the transition consistently

Example fix

# before: policy says encrypt, but the blob is still plaintext
[history.encryption]
recipients = ["age1..."]

# after: re-run the checkpoint after the encryption migration so the
# stored object begins with 'mise-encrypted-file-v1\n', then omit safely
Defensive patterns

Strategy: validation

Validate before calling

# before relying on omission, confirm stored blobs are encrypted
mise history verify 2>/dev/null || \
  grep -q 'mise-encrypted-file-v1' <(mise history cat <path>) || \
  echo "path still stored as plaintext; re-encrypt before omitting"

Prevention

When it happens

Trigger: Running a checkpoint/attempt_locked flow where a file is newly covered by [history] encryption policy, the omission list references that file, and its blob content does not start with the mise-encrypted-file-v1 header.

Common situations: Adding encryption recipients to history.encryption while files were previously captured in plaintext; a partial encryption migration where the policy says encrypt but the object store still holds the plaintext version.

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 jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/7ee9d477af3145b5. Report an issue: GitHub.

Appendix: source

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

        .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 533346cc37)