jdx/mise · error
cannot encrypt non-file
Error message
cannot encrypt non-file {} What it means
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.
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).
Example fix
// before: policy tracks everything under a dir that contains a socket files = ["~/.service/**"] // after: exclude non-regular entries files = ["~/.service/**", "!~/.service/daemon.sock"]
Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::symlink_metadata(&live)?;
if !meta.is_file() && !meta.file_type().is_symlink() {
eprintln!("skipping non-file: {}", live.display());
return Ok(()); // or exclude from policy before capture
} Type guard
fn is_capturable(meta: &std::fs::Metadata) -> bool {
meta.is_file() || meta.file_type().is_symlink()
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- cannot capture non-file
- cannot protect
- cannot safely change
- cannot verify current files for restoration
- {other:?}
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/1e7b6996dae99225.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/history/shadow.rs:426
(
"120000",
path_bytes(&std::fs::read_link(&live)?).into_owned(),
)
} else if metadata.is_file() {
#[cfg(unix)]
let executable = {
use std::os::unix::fs::PermissionsExt;
metadata.permissions().mode() & 0o100 != 0
};
#[cfg(not(unix))]
let executable = false;
let bytes = crate::agecrypt::read_bounded(
std::fs::File::open(&live)?,
crate::agecrypt::MAX_PLAINTEXT_BYTES,
)?;
(if executable { "100755" } else { "100644" }, bytes)
} else {
bail!("cannot encrypt non-file {}", display_path(&live));
};
let path = format!(
"{}/{}",
root.label,
rel.to_str()
.ok_or_else(|| eyre::eyre!("non-UTF-8 tracked path"))?
.replace('\\', "/")
);
let fingerprint = blake3::keyed_hash(&cache_key, &bytes).to_hex().to_string();
let cached = cache.get(&path).filter(|entry| {
entry.fingerprint == fingerprint && entry.mode == mode && entry.scheme == scheme
});
let oid = match cached {
Some(entry)
if self
.blob_starts_with(&entry.oid, b"mise-encrypted-file-v1\n")
.unwrap_or(false) =>
{View on GitHub (pinned to 533346cc37)