jdx/mise · error

cannot capture non-file {}

Error message

cannot capture non-file {}

What it means

The non-encrypted capture path (tree_for_root via capture) hashes each live tracked path as a git blob, which requires a regular file. If a tracked path is not a regular file at capture time (directory, dangling symlink, special file), the snapshot cannot represent it and capture bails naming the path.

Source

Thrown at src/system/history/shadow.rs:435

                    ("160000", crate::git::Git::new(&live).current_sha()?)
                } else if meta.is_file() {
                    #[cfg(unix)]
                    let executable = {
                        use std::os::unix::fs::PermissionsExt;
                        meta.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" },
                        self.hash_blob(&bytes)?,
                    )
                } else {
                    bail!("cannot capture non-file {}", display_path(&live));
                };
                Ok(Overlay {
                    path: rel
                        .to_str()
                        .ok_or_else(|| eyre::eyre!("non-UTF-8 tracked path"))?
                        .replace('\\', "/"),
                    object: Some((mode.into(), oid)),
                })
            })();
            match captured {
                Ok(overlay) => overlays.push(overlay),
                Err(error) => omitted.push(super::store::PathReason {
                    path: display_path(&live),
                    reason: format!("unreadable: {error:#}"),
                }),
            }
        }
        self.compose(&self.empty_object("tree")?, &overlays)

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Check the reported path with `ls -la` and remove or fix the non-file entry
  2. Restore a deleted symlink target or delete the dangling symlink
  3. Update the tracked-path configuration to reference actual files, then re-run capture

Example fix

// before: dangling symlink at tracked path
~/.config/app/settings.json -> /mnt/usb/settings.json (missing)

// after: real file present
~/.config/app/settings.json (regular file)
Defensive patterns

Strategy: validation

Validate before calling

// Rust: stat all tracked paths before calling capture
for p in &tracked {
    let m = std::fs::symlink_metadata(p)
        .with_context(|| format!("tracked path missing: {}", p.display()))?;
    anyhow::ensure!(m.is_file(), "tracked path is not a file: {}", p.display());
}

Type guard

fn tracked_is_file(p: &std::path::Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

if let Err(e) = capture(&roots) {
    if e.to_string().starts_with("cannot capture non-file") {
        eprintln!("snapshot skipped: {e}; fix the path and re-run");
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: capture (via tree_for_root) walks a tracked root and a tracked path resolves to a non-regular file when it attempts to read and hash its bytes.

Common situations: A path previously tracked as a file was replaced by a directory; a symlink's target was removed; a FIFO/socket appeared at a tracked path; filesystem race where the file was deleted between the walk and the read.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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