clockworklabs/SpacetimeDB · error · io::Error

failed to open directory {} for fsync: {}

Error message

failed to open directory {} for fsync: {}

What it means

Part of the snapshot crate's durability sync (SyncTarget::sync_all): on Unix, a newly written snapshot file's enclosing directory must also be fsynced so the directory entry itself survives a crash. This variant wraps the failure to File::open the directory path, preserving the original error kind - the directory could not be opened at all.

Source

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

    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 524b4487d9)

Solutions

  1. Stop concurrent deletion/cleanup jobs that race with snapshot creation.
  2. Verify the configured snapshot directory exists and is writable before starting.
  3. Fix permissions on the snapshot root.
  4. Retry the snapshot once the environment is stable.
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;

fn snapshot_dir_ready(dir: &str) -> std::io::Result<()> {
    let md = fs::metadata(dir)?;
    if !md.is_dir() {
        return Err(std::io::Error::new(std::io::ErrorKind::NotADirectory, dir));
    }
    Ok(())
}

Try / catch

match sync_result {
    Err(e) if e.to_string().contains("failed to open directory") => {
        // Directory vanished or is not a directory: stop racing cleanup jobs,
        // recreate the directory, then retry the snapshot.
    }
    r => r,
}

Prevention

When it happens

Trigger: Saving/syncing a snapshot when the snapshot directory was deleted or renamed concurrently (cleanup job racing the snapshot); the path exists but is not a directory (ENOTDIR); the process lacks permission on the directory.

Common situations: Concurrent snapshot GC deleting directories while a snapshot is being finalized; snapshot root misconfigured to a file path; permission changes on a shared volume.

Related errors


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