zed-industries/zed · error · std::io::Error
sandbox write grant {} is a symlink, not a directory
Error message
sandbox write grant {} is a symlink, not a directory What it means
Validation error in CanonicalPathBuf::from_canonical: the configured sandbox write grant path resolves (via an O_NOFOLLOW open) to a symlink rather than a real directory. Symlink grants are rejected because the sandbox must pin a concrete directory inode, not a path that can be redirected.
Source
Thrown at crates/sandbox/src/util/canonical_path.rs:150
{
use std::os::unix::fs::OpenOptionsExt as _;
// `O_NOFOLLOW` makes a symlink *leaf* open the symlink itself
// (harmless with `O_PATH`) rather than its target, so we can detect
// and reject it below; intermediate components are still traversed
// and caught by the canonical-path comparison.
let file = std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_PATH | libc::O_CLOEXEC | libc::O_NOFOLLOW)
.open(&path)?;
let fd = OwnedFd::from(file);
// Reject a symlink leaf outright: a grant must name a real directory,
// and `readlink` of an `O_PATH|O_NOFOLLOW` fd on a symlink returns
// the symlink's *own* path (equal to `path`), so the comparison
// below wouldn't catch it.
let stat = nix::sys::stat::fstat(fd.as_raw_fd()).map_err(io::Error::from)?;
if stat.st_mode & libc::S_IFMT == libc::S_IFLNK {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"sandbox write grant {} is a symlink, not a directory",
path.display()
),
));
}
// Load-bearing: the pinned inode's real path must still be exactly
// the approved canonical path. If any component became a symlink
// after approval, the fd resolves elsewhere and this diverges.
let current = std::fs::read_link(format!("/proc/self/fd/{}", fd.as_raw_fd()))?;
if current != path {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"sandbox write grant {} was redirected to {}",
path.display(),View on GitHub (pinned to f4178619ac)
Solutions
- Grant the real target directory instead of the symlink
- Remove the symlink and replace it with an actual directory
- If a symlink is intentional, grant its resolved canonical target
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at crates/sandbox/src/util/canonical_path.rs:150 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/c82f99811096e917.
Report an issue: GitHub.