gastownhall/beads · error

failed to open %s securely: %w

Error message

failed to open %s securely: %w

What it means

After lstat checks pass, fixBeadsDirPermissions opens the directory via a secure directory handle (openBeadsDirHandle, O_DIRECTORY/no-follow based) so the subsequent chmod operates on the opened handle rather than the path. This error wraps any failure from that secure open, such as openDir returning ENOENT, ELOOP, ENOTDIR, or permission denial on open.

Source

Thrown at internal/config/permissions.go:69

		if os.IsNotExist(err) {
			return false, nil // directory doesn't exist yet
		}
		return false, fmt.Errorf("failed to inspect %s: %w", path, err)
	}
	if info.Mode()&os.ModeSymlink != 0 {
		return false, fmt.Errorf("refusing to chmod %s: path is a symbolic link", path)
	}
	if !info.IsDir() {
		return false, fmt.Errorf("refusing to chmod %s: path is not a directory", path)
	}
	perm := info.Mode().Perm()
	if perm&0077 == 0 {
		return false, nil // no group or world-accessible bits
	}

	dir, err := openDir(path)
	if err != nil {
		return false, fmt.Errorf("failed to open %s securely: %w", path, err)
	}
	defer func() { _ = dir.Close() }()

	openedInfo, err := dir.Stat()
	if err != nil {
		return false, fmt.Errorf("failed to inspect opened directory %s: %w", path, err)
	}
	if !openedInfo.IsDir() || !os.SameFile(info, openedInfo) {
		return false, fmt.Errorf("refusing to chmod %s: path changed during permission repair", path)
	}
	if err := dir.Chmod(BeadsDirPerm); err != nil {
		return false, fmt.Errorf("failed to chmod %s to %04o: %w", path, BeadsDirPerm, err)
	}
	return true, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the operation — transient races between Lstat and open usually resolve on retry.
  2. Confirm the path is still a real directory: ls -ld <path>.
  3. Check parent-directory execute permissions (chmod +x on ancestors as needed).
  4. If the filesystem (e.g. some FUSE/network mounts) does not support secure directory opens, chmod the directory manually: chmod 700 <path>.

Example fix

// fallback when secure open is unsupported
changed, err := config.FixBeadsDirPermissions(beadsDir)
if err != nil {
    _ = os.Chmod(beadsDir, 0700) // manual fallback
}
Defensive patterns

Strategy: retry

Validate before calling

info, err := os.Lstat(beadsDir)
if err != nil || !info.IsDir() {
    return fmt.Errorf("%s is not a usable directory", beadsDir)
}
// check ancestors are searchable
for dir := filepath.Dir(beadsDir); ; dir = filepath.Dir(dir) {
    if st, err := os.Stat(dir); err != nil || st.Mode().Perm()&0001 == 0 {
        return fmt.Errorf("cannot traverse %s", dir)
    }
    if dir == "/" { break }
}

Try / catch

changed, err := config.FixBeadsDirPermissions(beadsDir)
if err != nil {
    if strings.Contains(err.Error(), "failed to open") {
        time.Sleep(50 * time.Millisecond)
        changed, err = config.FixBeadsDirPermissions(beadsDir) // one retry for races
    }
    if err != nil {
        log.Printf("permission fix failed, chmod manually: %v", err)
    }
}

Prevention

When it happens

Trigger: Calling FixBeadsDirPermissions on a path whose group/world bits are set, and the secure open of the directory fails — e.g. the directory was deleted or replaced between Lstat and open, the open syscall flags are unsupported on the platform/filesystem, or the process lacks search permission on a parent directory.

Common situations: Race where the directory is removed while repairing; network/overlay filesystems that do not support O_DIRECTORY or no-follow opens; running without execute permission on a parent directory; sandboxed environments restricting openat flags.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/5a8fb4206755747e. Report an issue: GitHub.