{"record":{"id":"7ee9d477af3145b5","repo":"jdx/mise","slug":"cannot-retain-an-unreadable-plaintext-version-of-n","errorCode":null,"errorMessage":"cannot retain an unreadable plaintext version of newly encrypted {}","messagePattern":"cannot retain an unreadable plaintext version of newly encrypted (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/checkpoint.rs","lineNumber":926,"sourceCode":"        .flat_map(|path| path.ancestors().skip(1))\n        .collect();\n    for file in repo.ls_tree(parent)? {\n        let located = roots.locate(&file.path);\n        let Some(path) = located.path() else { continue };\n        if !omitted.iter().any(|omitted| path.starts_with(omitted))\n            || !tracked.would_retain(path)?\n            || repo.object_at(tree, &file.path)?.is_some()\n        {\n            continue;\n        }\n        let Some(entry) = tracked.entry_for(path) else {\n            continue;\n        };\n        if entry.tree_path(path)? != file.path {\n            continue;\n        }\n        if entry.policy.encrypt && !repo.blob_starts_with(&file.oid, b\"mise-encrypted-file-v1\\n\")? {\n            eyre::bail!(\n                \"cannot retain an unreadable plaintext version of newly encrypted {}\",\n                display_path(path)\n            );\n        }\n        let display = display_path(path);\n        modes.remove(&display);\n        if let Some(mode) = record.tree.modes.get(&display) {\n            modes.insert(display, *mode);\n        }\n        for parent in path\n            .ancestors()\n            .skip(1)\n            .take_while(|parent| parent.starts_with(&entry.path))\n        {\n            if !observed_directories.contains(parent) {\n                let display = display_path(parent);\n                if let Some(mode) = record.tree.modes.get(&display) {\n                    modes.insert(display, *mode);","sourceCodeStart":908,"sourceCodeEnd":944,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/checkpoint.rs#L908-L944","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-capture or re-checkpoint the file so it is stored in the encrypted format under the current policy","Verify history.encryption recipients are configured and the encryption layer actually rewrote the file's blob (check it starts with mise-encrypted-file-v1)","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"],"exampleFix":"# before: policy says encrypt, but the blob is still plaintext\n[history.encryption]\nrecipients = [\"age1...\"]\n\n# after: re-run the checkpoint after the encryption migration so the\n# stored object begins with 'mise-encrypted-file-v1\\n', then omit safely","handlingStrategy":"validation","validationCode":"# before relying on omission, confirm stored blobs are encrypted\nmise history verify 2>/dev/null || \\\n  grep -q 'mise-encrypted-file-v1' <(mise history cat <path>) || \\\n  echo \"path still stored as plaintext; re-encrypt before omitting\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Complete encryption migrations in one step, then re-checkpoint affected files","Never leave the encryption policy half-applied across captures","Verify blob headers after changing recipients"],"tags":["encryption","history","checkpoint","invariant"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}