clockworklabs/SpacetimeDB · error · io::Error

failed to open directory

Error message

failed to open directory {} for fsync: {}

What it means

crates/snapshot's sync helper fsyncs both files and (on Unix) their enclosing directories for durability. For Self::Dir(path) it must File::open the directory first, since directories are opened for fsync rather than created. If that open fails, the original io::Error kind is preserved and wrapped with 'failed to open directory {path} for fsync: {source}'. Typical underlying causes: the directory was deleted or renamed between snapshot and sync, permissions changed, or the path is not a directory.

Solutions

  1. Check that the directory still exists and is a directory right before syncing; re-create it if the workflow allows
  2. Fix permissions so the process can open (read) the directory it needs to sync
  3. Serialize directory cleanup with snapshot finalization so they cannot race
  4. Match on io::ErrorKind in the caller (NotFound/PermissionDenied) to decide between recreate, retry, and abort

Example fix

// before
SyncTarget::Dir(&dir).sync_all()?; // NotFound: dir removed by cleaner mid-snapshot

// after
if !dir.is_dir() {
    std::fs::create_dir_all(&dir)?; // or: skip sync if dir intentionally gone
}
SyncTarget::Dir(&dir).sync_all()?;
Defensive patterns

Strategy: try-catch

Validate before calling

if !dir.as_path().is_dir() {
    std::fs::create_dir_all(&dir)?; // or skip if legitimately gone
}

Type guard

fn is_syncable_dir(p: &Path) -> bool { p.is_dir() }

Try / catch

if let Err(e) = SyncTarget::Dir(&dir).sync_all() {
    if e.kind() == std::io::ErrorKind::NotFound { /* recreated/removed concurrently: decide policy */ }
    else if e.kind() == std::io::ErrorKind::PermissionDenied { /* fix perms */ }
    else { return Err(e); }
}

Prevention

When it happens

Trigger: Snapshot/restore code syncing a directory that a concurrent process removed or moved; running under a user without read (open) permission on the directory; a path that points at a file rather than a directory; container/overlay filesystems that reject opening directories.

Common situations: Concurrent cleanup (tmp cleaners, GC of snapshot dirs) racing with durability sync; restrictive permissions in hardened deployments; NFS/FUSE mounts with odd open semantics for directories.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20). Data as JSON: /api/errors/801af999b94def3a. Report an issue: GitHub.

Appendix: source

Thrown at crates/snapshot/src/lib.rs:1630

    Dir(&'a Path),
}

impl FileOrDirPath<'_> {
    /// `fsync` the file or directory at path `self`.
    ///
    /// On *nix systems, both a file and its enclosing directory should be
    /// `fsync`ed to make the file durable.
    ///
    /// On Windows, only the file needs to be synced, and it's even an error to
    /// sync a directory. Passing in [Self::Dir] is thus a no-op on Windows.
    fn sync_all(&self) -> io::Result<()> {
        match self {
            #[cfg(target_os = "windows")]
            Self::Dir(path) => Ok(()),
            #[cfg(not(target_os = "windows"))]
            Self::Dir(path) => File::open(path)
                .map_err(|e| {
                    io::Error::new(
                        e.kind(),
                        format!("failed to open directory {} for fsync: {}", path.display(), e),
                    )
                })?
                .sync_all()
                .map_err(|e| io::Error::new(e.kind(), format!("failed to fsync directory {}: {}", path.display(), e))),
            Self::File(path) => {
                File::options()
                    .read(true)
                    // Windows needs the file to be writable for `sync_all` to work.
                    // Set all the open options explicitly, just for visibility.
                    .write(true)
                    .truncate(false)
                    .create(false)
                    .append(false)
                    .open(path)
                    .map_err(|e| {
                        io::Error::new(

View on GitHub (pinned to 9e0d92412f)