pnpm/pnpm · error · std::io::Error

CAFS shard path {} exists but does not resolve to a director

Error message

CAFS shard path {} exists but does not resolve to a directory

What it means

When initializing the content-addressable store (CAFS), pacquet ensures each shard directory exists. If a shard path exists but Path::is_dir() - which follows symlinks - is false (a regular file, a non-directory symlink, or a broken symlink occupies it), initialization fails fast with AlreadyExists and the shard path. This upfront rejection prevents a much less actionable raw `open` error later during per-file CAFS writes.

Source

Thrown at pnpm/crates/store-dir/src/store_dir.rs:245

            let shard_dir = files.join(format!("{shard:02x}"));
            if let Err(error) = std::fs::create_dir(&shard_dir) {
                if error.kind() != std::io::ErrorKind::AlreadyExists {
                    return Err(error);
                }
                // `AlreadyExists` is benign only when the existing
                // entry resolves to a directory — a parallel pnpm
                // or pacquet process racing the same layout is
                // fine, and a symlink pointing at a real directory
                // is too (ops folks occasionally spread a store
                // across disks that way). `Path::is_dir` follows
                // symlinks, which is the desired semantics here. A
                // regular file, a non-dir symlink, or a broken
                // symlink would make `mark_shard_ensured` a lie and
                // punt the failure to a much less actionable
                // `open` error inside the per-file CAFS write.
                // Reject upfront.
                if !shard_dir.is_dir() {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::AlreadyExists,
                        format!(
                            "CAFS shard path {} exists but does not resolve to a directory",
                            shard_dir.display(),
                        ),
                    ));
                }
            }
            self.mark_shard_ensured(shard);
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests;

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Inspect the exact path printed in the message (ls -l) to see what occupies it
  2. If it is a stray file or broken symlink, delete it and rerun the install so the shard directory is created
  3. If the symlink was intentional, fix it to point at an existing directory on a mounted disk
  4. As a last resort, move the whole store aside and let pnpm recreate it (costs re-fetching)

Example fix

# before
ls -l ~/.local/share/pnpm/store/v10/files/3f
# -rw-r--r-- 1 me me 4096 ... 3f   (a file where a dir is needed)

# after
rm ~/.local/share/pnpm/store/v10/files/3f
pnpm install
Defensive patterns

Strategy: validation

Validate before calling

fn store_shards_ensure_ok(root: &Path) -> Result<(), String> {
    for shard in 0u8..=255 {
        let p = root.join(format!("files/{:02x}", shard));
        match std::fs::symlink_metadata(&p) {
            Ok(m) if m.is_dir() || m.file_type().is_symlink() && p.is_dir() => {}
            Ok(_) => return Err(format!("shard path occupied by non-directory: {}", p.display())),
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
            Err(e) => return Err(e.to_string()),
        }
    }
    Ok(())
}

Type guard

fn is_shard_not_a_directory(err: &std::io::Error) -> bool {
    err.kind() == std::io::ErrorKind::AlreadyExists
        && err.to_string().contains("does not resolve to a directory")
}

Try / catch

if let Err(e) = store_dir.ensure() {
    if is_shard_not_a_directory(&e) {
        // path is in the message: inspect, remove the stray file/broken link, retry once
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Store initialization where a path like <store>/files/<shard> exists as a plain file or as a symlink that does not resolve to a directory: manual tampering, an interrupted store migration, or a symlinked store whose target disk was unmounted or moved.

Common situations: Store spread across disks via symlinks and the target volume removed; partial restore/copy of a store directory tree; another tool writing files into the store path; network filesystem with a dead link.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/067fe88828474bfe. Report an issue: GitHub.