Hmbown/CodeWhale · error

could not securely open

Error message

could not securely open {}: {error}

What it means

The Unix open path for the workspace `.env` uses `O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK` so symlinks are never followed and a FIFO named `.env` cannot hang startup. If `open` fails (including the ELOOP that O_NOFOLLOW produces for symlinks) it is wrapped in this error.

Solutions

  1. Replace the `.env` symlink with a real regular file copy inside the workspace.
  2. Fix file permissions so the running user can read it.
  3. Create the file if it is missing, or unset the expectation of a workspace `.env` for this project.

Example fix

// before (shell)
ln -s ~/secrets/.env .env
// after
cp ~/secrets/.env .env && chmod 600 .env
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(".env")?;
if md.file_type().is_symlink() {
    eprintln!(".env must be a regular file, not a symlink");
}
if !md.is_file() { eprintln!(".env is not a regular file"); }

Type guard

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

Try / catch

match load_workspace_dotenv_credentials_from_path(&path) {
    Err(e) if e.to_string().contains("could not securely open") => {
        eprintln!(".env must be a readable regular file (no symlinks): {e:#}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: `OpenOptions::open(path)` returns an io::Error: `ELOOP` (path is a symlink, rejected by O_NOFOLLOW), `ENOENT` (missing file), `EACCES` (permissions), or a non-regular file type.

Common situations: A `.env` that is actually a symlink (common when people symlink shared dotfiles), or one unreadable due to permissions/ownership.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/99f6438dfce79dac. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lib.rs:3023

#[cfg(not(any(unix, windows)))]
fn workspace_dotenv_has_multiple_links(
    _file: &std::fs::File,
    _metadata: &std::fs::Metadata,
) -> Result<bool> {
    Ok(false)
}

#[cfg(unix)]
fn open_workspace_dotenv_without_following_links(path: &Path) -> Result<std::fs::File> {
    use std::os::unix::fs::OpenOptionsExt;

    std::fs::OpenOptions::new()
        .read(true)
        // `O_NONBLOCK` is inert for regular files but prevents a FIFO named
        // `.env` from hanging startup before the metadata check can reject it.
        .custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK)
        .open(path)
        .map_err(|error| anyhow!("could not securely open {}: {error}", path.display()))
}

#[cfg(windows)]
fn open_workspace_dotenv_without_following_links(path: &Path) -> Result<std::fs::File> {
    use std::os::windows::fs::{MetadataExt, OpenOptionsExt};

    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
    const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
    let file = std::fs::OpenOptions::new()
        .read(true)
        .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
        .open(path)
        .map_err(|error| anyhow!("could not securely open {}: {error}", path.display()))?;
    let metadata = file
        .metadata()
        .map_err(|error| anyhow!("could not inspect {}: {error}", path.display()))?;
    if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
        bail!(

View on GitHub (pinned to 73e0f67d83)