charmbracelet/crush · error

read config file: %w

Error message

read config file: %w

What it means

atomicWrite reads the current config file before applying the transform callback. Missing files are treated as empty config ({}), but any other read error — permission denied, path is a directory, I/O error — is wrapped as this error and aborts the write.

Source

Thrown at internal/config/store.go:316

// new contents. fn must be pure — no I/O, no network calls.
func (s *ConfigStore) atomicWrite(scope Scope, fn func(current []byte) ([]byte, error)) error {
	unlock, err := s.lockConfig(scope)
	if err != nil {
		return err
	}
	defer unlock()

	path, err := s.configPath(scope)
	if err != nil {
		return err
	}

	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			data = []byte("{}")
		} else {
			return fmt.Errorf("read config file: %w", err)
		}
	}

	newData, err := fn(data)
	if err != nil {
		return err
	}

	return atomicWriteFile(path, newData, 0o600)
}

// configPath returns the file path for the given scope.
func (s *ConfigStore) configPath(scope Scope) (string, error) {
	switch scope {
	case ScopeWorkspace:
		if s.workspacePath == "" {
			return "", ErrNoWorkspaceConfig
		}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Fix the file's permissions so the current user can read it (chmod u+r)
  2. Check whether the config path is a directory (ls -la) and remove/rename it
  3. Verify ownership matches the running user (chown)
  4. Restore the config file from backup or delete it so it is recreated as {}

Example fix

// before
// file exists but unreadable:
os.WriteFile(cfgPath, data, 0o000)
store.RemoveConfigField(scope, key) // fails
// after
if info, err := os.Stat(cfgPath); err == nil && info.Mode().Perm()&0o400 == 0 {
    os.Chmod(cfgPath, 0o600) // ensure owner-readable before writing
}
store.RemoveConfigField(scope, key)
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(configPath)
if err == nil {
    if info.IsDir() {
        return fmt.Errorf("%s is a directory, expected a file", configPath)
    }
    if f, err := os.Open(configPath); err != nil {
        return fmt.Errorf("config file unreadable: %w", err)
    } else { f.Close() }
}

Type guard

func configReadable(path string) bool {
    f, err := os.Open(path)
    if err != nil { return false }
    f.Close()
    return true
}

Try / catch

var perr *fs.PathError
if errors.As(err, &perr) && errors.Is(perr, fs.ErrPermission) {
    os.Chmod(perr.Path, 0o600)
    // retry the operation once
}

Prevention

When it happens

Trigger: Calling writeConfigFields or RemoveConfigField when the config file exists but cannot be read: the file's mode denies read access, the path is actually a directory, or a low-level I/O error occurs. (Not triggered when the file is simply absent.)

Common situations: Config file created by another user/root with restrictive modes; a directory accidentally created at the config file's path; corrupted filesystem; running under a different user via sudo so ownership no longer matches.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/b917b63572327d3e. Report an issue: GitHub.