gastownhall/beads · error

ensureProxiedServerConfig: mkdir %s: %w

Error message

ensureProxiedServerConfig: mkdir %s: %w

What it means

When no custom config is supplied, ensureProxiedServerConfig creates the config's parent directory with config.BeadsDirPerm before writing the generated YAML. This error wraps os.MkdirAll failure for that parent directory. It means bd could not create or traverse a directory component on the way to the config file.

Source

Thrown at cmd/bd/proxied_server.go:138

	path, isCustom, err := resolveProxiedServerConfigPath(beadsDir)
	if err != nil {
		return "", err
	}

	if isCustom {
		info, err := os.Stat(path)
		if err != nil {
			return "", fmt.Errorf("ensureProxiedServerConfig: custom config %s: %w", path, err)
		}
		if !info.Mode().IsRegular() {
			return "", fmt.Errorf("ensureProxiedServerConfig: custom config %s: not a regular file", path)
		}
		return path, nil
	}

	root := filepath.Dir(path)
	if err := os.MkdirAll(root, config.BeadsDirPerm); err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: mkdir %s: %w", root, err)
	}

	switch _, err := os.Stat(path); {
	case err == nil:
		return path, nil
	case !os.IsNotExist(err):
		return "", fmt.Errorf("ensureProxiedServerConfig: stat %s: %w", path, err)
	}

	port, err := proxy.PickFreePort()
	if err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: pick free port: %w", err)
	}

	body, err := renderProxiedServerConfig(port)
	if err != nil {
		return "", fmt.Errorf("ensureProxiedServerConfig: render YAML: %w", err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped os error: if it is 'not a directory', check whether an ancestor path component (like ~/.beads) is a file and remove/rename it (`ls -ld ~/.beads`).
  2. Check write/traverse permissions on each ancestor: `ls -ld $(dirname -a <path>)` and `chmod u+w` the directory that denies writes.
  3. If running in a container or sandbox, ensure $HOME (or BEADS_DIR equivalent) points to a writable location, or run with appropriate user/permissions.
  4. Free disk space / fix full-device conditions if the error is ENOSPC.
  5. As a last resort pass a custom config path on a writable filesystem instead of the default location.

Example fix

// before
$ ls -ld ~/.beads
-rw-r--r-- 1 user user 0 ~/.beads      # a file where a dir is required
// after
$ rm ~/.beads && bd serve                # bd recreates ~/.beads/ with BeadsDirPerm
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(cfgPath)
if st, err := os.Stat(dir); err == nil && !st.IsDir() {
	return fmt.Errorf("%s exists but is not a directory", dir)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
	return err
}

Type guard

func isWritableDir(path string) bool {
	info, err := os.Stat(path)
	if err != nil || !info.IsDir() {
		return false
	}
	f, err := os.CreateTemp(path, ".wtest*")
	if err != nil {
		return false
	}
	f.Close()
	os.Remove(f.Name())
	return true
}

Prevention

When it happens

Trigger: os.MkdirAll(filepath.Dir(path), config.BeadsDirPerm) fails because a path component exists as a non-directory (e.g. `~/.beads` is a regular file), the parent is read-only, or permission denies traversal (unreadable $HOME, root-owned dir, restricted sandbox).

Common situations: $HOME not writable (CI containers running as non-root with odd HOME); ~/.beads accidentally created as a file; read-only container filesystem or read-only $HOME mount; NFS/network home with permission quirks; sandboxed environments blocking writes outside the workspace.

Related errors


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