gastownhall/beads · error

%s: %w

Error message

%s: %w

What it means

cleanAbsoluteUserDirectory validates a candidate user-directory path during config path resolution. If the directory resolution step (e.g. os.UserHomeDir or os.UserConfigDir) returned an error, it is wrapped as "<label>: <cause>"; if the resolved path is not absolute after cleaning, a separate "not an absolute native path" error is produced.

Source

Thrown at internal/config/user_config_path.go:51

	if home, err := cleanAbsoluteUserDirectory("user home directory", homeDir, homeErr); err != nil {
		candidates.homeErr = err
	} else {
		candidates.legacy = filepath.Clean(filepath.Join(home, ".beads", "config.yaml"))
		candidates.documented = filepath.Clean(filepath.Join(home, ".config", "bd", "config.yaml"))
	}

	if nativeDir, err := cleanAbsoluteUserDirectory("native user config directory", nativeConfigDir, nativeErr); err != nil {
		candidates.nativeErr = err
	} else {
		candidates.native = filepath.Clean(filepath.Join(nativeDir, "bd", "config.yaml"))
	}

	return candidates
}

func cleanAbsoluteUserDirectory(label, path string, resolutionErr error) (string, error) {
	if resolutionErr != nil {
		return "", fmt.Errorf("%s: %w", label, resolutionErr)
	}
	cleaned := filepath.Clean(path)
	if !filepath.IsAbs(cleaned) {
		return "", fmt.Errorf("%s %q is not an absolute native path", label, path)
	}
	return cleaned, nil
}

// UserConfigYamlPath resolves the user-level config.yaml to a cleaned,
// absolute path suitable for native filesystem APIs. It prefers the documented
// <home>/.config/bd location when that file exists, then an existing native
// os.UserConfigDir location. For a new file it keeps the documented location
// as the creation target when possible, falling back to the native location
// only when the home directory itself cannot be resolved safely.
func UserConfigYamlPath() (string, error) {
	return selectUserConfigYamlPath(currentUserConfigYamlCandidates())
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Ensure HOME (or USERPROFILE on Windows) is set to an absolute existing directory
  2. Run as a user that has a valid home directory (check /etc/passwd entry)
  3. Export HOME=/path/to/dir in containers/CI before running bd
  4. Unwrap the error to see the underlying OS resolver failure

Example fix

// before
bd doctor // fails: "home directory: $HOME is not defined"
// after (Dockerfile/CI)
ENV HOME=/root
docker run -e HOME=/root ... bd doctor
Defensive patterns

Strategy: validation

Validate before calling

home := os.Getenv("HOME") // or USERPROFILE on Windows
if home == "" {
    return errors.New("HOME is not set; set it to an absolute directory")
}
if fi, err := os.Stat(home); err != nil || !fi.IsDir() || !filepath.IsAbs(home) {
    return fmt.Errorf("HOME=%q is not an absolute existing directory", home)
}

Type guard

func hasValidHome() bool {
    h, err := os.UserHomeDir()
    return err == nil && filepath.IsAbs(h)
}

Try / catch

if err := resolveUserConfigPath(); err != nil {
    var labeled *wrapErr
    if errors.As(err, &labeled) {
        log.Printf("resolution failed at %s: %v", labeled.Label(), errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: A resolution function passed in as resolutionErr fails (HOME unset, no home directory for the user, unsupported platform) or the resolved path is relative/non-native when resolving user config directory candidates.

Common situations: Running in a container or CI where $HOME is unset or points to a nonexistent dir; running as a system user without a home directory; Windows paths leaking into POSIX checks or vice versa in cross-compiled binaries.

Related errors


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