gastownhall/beads · error

failed to create user config directory: %w

Error message

failed to create user config directory: %w

What it means

SetUserYamlConfig resolves the user config path via UserConfigYamlPath, then ensures the parent directory exists with os.MkdirAll(dir, 0o755). If directory creation fails, the error is wrapped as 'failed to create user config directory'. This happens before any file is created or written.

Source

Thrown at internal/config/yaml_config.go:495

	// uses (SetUserYamlConfig, setYamlConfigAtPath, the metrics bootstrap);
	// rewriting at 0644 would relax this shared user config to world-readable.
	if err := os.WriteFile(configPath, []byte(newContent), 0o600); err != nil { //nolint:gosec // configPath is from UserConfigYamlPath
		return fmt.Errorf("failed to write user config.yaml: %w", err)
	}

	return nil
}

func SetUserYamlConfig(key, value string) error {
	if err := validateYamlConfigValue(key, value); err != nil {
		return err
	}
	configPath, err := UserConfigYamlPath()
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
		return fmt.Errorf("failed to create user config directory: %w", err)
	}
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		if err := os.WriteFile(configPath, []byte{}, 0o600); err != nil {
			return fmt.Errorf("failed to create user config.yaml: %w", err)
		}
	} else if err != nil {
		return fmt.Errorf("failed to stat user config.yaml: %w", err)
	}
	return setYamlConfigAtPath(configPath, key, value)
}

func setYamlConfigAtPath(configPath, key, value string) error {

	// Normalize key to canonical yaml format
	normalizedKey := normalizeYamlKey(key)

	// Read existing config
	content, err := os.ReadFile(configPath) //nolint:gosec // configPath is from findProjectConfigYaml

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure a file named like the config dir doesn't already exist (e.g. ~/.beads as a file) — remove or rename it.
  2. Fix HOME/USERPROFILE to a writable absolute path and retry.
  3. Check permissions on the parent (chmod u+w ~) and free disk space if ENOSPC.
  4. Read the wrapped %w cause (EACCES/ENOTDIR/EROFS) and fix the specific filesystem condition.

Example fix

// before
$ ls -la ~ | grep beads
-rw-r--r-- 1 user user .beads     # a file, not a dir
bd config set ... // mkdir fails ENOTDIR
// after
mv ~/.beads ~/.beads.bak
mkdir -p ~/.beads
bd config set ...
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Join(os.Getenv("HOME"), ".beads")
if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
    return fmt.Errorf("%s exists but is not a directory", dir)
}
if err := unix.Access(filepath.Dir(dir), unix.W_OK); err != nil {
    return fmt.Errorf("parent %s not writable: %w", filepath.Dir(dir), err)
}

Try / catch

err := config.SetUserYamlConfig(key, value)
if err != nil && strings.Contains(err.Error(), "failed to create user config directory") {
    if mkErr := os.MkdirAll(homeDir, 0o755); mkErr != nil {
        return fmt.Errorf("unwritable HOME; set HOME to a writable dir: %w", mkErr)
    }
    return config.SetUserYamlConfig(key, value)
}

Prevention

When it happens

Trigger: os.MkdirAll on the user config directory (e.g. ~/.beads) fails because HOME is unwritable, a non-directory file exists at the target path, or permission/ENOSPC/EROFS conditions apply.

Common situations: HOME pointing to a read-only or nonexistent location; a regular file named .beads blocking directory creation; restricted sandboxed CI runners with read-only HOME.

Related errors


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