{"record":{"id":"1e7b6996dae99225","repo":"jdx/mise","slug":"cannot-encrypt-non-file","errorCode":null,"errorMessage":"cannot encrypt non-file {}","messagePattern":"cannot encrypt non-file (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/shadow.rs","lineNumber":426,"sourceCode":"                    (\n                        \"120000\",\n                        path_bytes(&std::fs::read_link(&live)?).into_owned(),\n                    )\n                } else if metadata.is_file() {\n                    #[cfg(unix)]\n                    let executable = {\n                        use std::os::unix::fs::PermissionsExt;\n                        metadata.permissions().mode() & 0o100 != 0\n                    };\n                    #[cfg(not(unix))]\n                    let executable = false;\n                    let bytes = crate::agecrypt::read_bounded(\n                        std::fs::File::open(&live)?,\n                        crate::agecrypt::MAX_PLAINTEXT_BYTES,\n                    )?;\n                    (if executable { \"100755\" } else { \"100644\" }, bytes)\n                } else {\n                    bail!(\"cannot encrypt non-file {}\", display_path(&live));\n                };\n                let path = format!(\n                    \"{}/{}\",\n                    root.label,\n                    rel.to_str()\n                        .ok_or_else(|| eyre::eyre!(\"non-UTF-8 tracked path\"))?\n                        .replace('\\\\', \"/\")\n                );\n                let fingerprint = blake3::keyed_hash(&cache_key, &bytes).to_hex().to_string();\n                let cached = cache.get(&path).filter(|entry| {\n                    entry.fingerprint == fingerprint && entry.mode == mode && entry.scheme == scheme\n                });\n                let oid = match cached {\n                    Some(entry)\n                        if self\n                            .blob_starts_with(&entry.oid, b\"mise-encrypted-file-v1\\n\")\n                            .unwrap_or(false) =>\n                    {","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/shadow.rs#L408-L444","documentation":"During encrypted-file capture, `capture_tracked_files` classifies each tracked encrypted path as a symlink, a regular file, or something else. Only symlinks and regular files can be read and encrypted; any other entry type (FIFO, socket, device node, or a path that vanished between listing and stat) hits this bail. It exists so the snapshot never attempts to read a non-regular file into an encrypted blob.","triggerScenarios":"A path matched by the encrypted-files policy is not a regular file or symlink when `capture_tracked` (via `capture_tracked_files`) stats it with `symlink_metadata`: e.g. a named pipe/socket/device node on the tracked path, or the entry was replaced/removed after the walk listed it (TOCTOU race, so `symlink_metadata` returned an other-type entry).","commonSituations":"Tracking an encrypted path that is actually a Unix socket or FIFO (e.g. a service socket under home), a device file, or a volatile path that swaps between file and non-file while a snapshot is taken. Also happens on filesystems where stat races occur under concurrent writes.","solutions":["Exclude the non-file path from the encrypted-files policy (globs) so it is not tracked/encrypted.","Verify what the path actually is (ls -l / stat); if it is a socket/FIFO/device, it cannot be snapshotted as a file.","If the path is expected to be a regular file, re-run capture after the writer finishes — this can be a transient race with the file being replaced.","Symlink to a regular file instead of tracking the non-file entry directly (symlinks are supported and captured as mode 120000)."],"exampleFix":"// before: policy tracks everything under a dir that contains a socket\nfiles = [\"~/.service/**\"]\n// after: exclude non-regular entries\nfiles = [\"~/.service/**\", \"!~/.service/daemon.sock\"]","handlingStrategy":"validation","validationCode":"let meta = std::fs::symlink_metadata(&live)?;\nif !meta.is_file() && !meta.file_type().is_symlink() {\n    eprintln!(\"skipping non-file: {}\", live.display());\n    return Ok(()); // or exclude from policy before capture\n}","typeGuard":"fn is_capturable(meta: &std::fs::Metadata) -> bool {\n    meta.is_file() || meta.file_type().is_symlink()\n}","tryCatchPattern":null,"preventionTips":["Keep sockets, FIFOs, and device paths out of encrypted-files globs.","Prefer explicit file lists over broad directory wildcards.","Avoid mutating tracked paths (replacing with special files) during capture."],"tags":["filesystem","encryption","snapshot"],"backgroundTag":"file-not-found","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"}