multica-ai/multica · error

read CLI config: %w

Error message

read CLI config: %w

What it means

LoadCLIConfigForProfile failed to read the profile's config.json from disk with an error other than 'file does not exist' (that case returns a zero config successfully). The underlying os.ReadFile error is wrapped, so the message ends with EACCES, EISDIR, or a similar errno. It only fires when the path resolved fine but the read itself was denied.

Source

Thrown at server/internal/cli/config.go:302

}

// LoadCLIConfig reads the CLI config from disk (default profile).
func LoadCLIConfig() (CLIConfig, error) {
	return LoadCLIConfigForProfile("")
}

// LoadCLIConfigForProfile reads the CLI config for the given profile.
func LoadCLIConfigForProfile(profile string) (CLIConfig, error) {
	path, err := CLIConfigPathForProfile(profile)
	if err != nil {
		return CLIConfig{}, err
	}
	data, err := os.ReadFile(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return CLIConfig{}, nil
		}
		return CLIConfig{}, fmt.Errorf("read CLI config: %w", err)
	}
	var cfg CLIConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		return CLIConfig{}, fmt.Errorf("parse CLI config: %w", err)
	}
	return cfg, nil
}

// SaveCLIConfig writes the CLI config to disk atomically (default profile).
func SaveCLIConfig(cfg CLIConfig) error {
	return SaveCLIConfigForProfile(cfg, "")
}

// SaveCLIConfigForProfile writes the CLI config for the given profile.
func SaveCLIConfigForProfile(cfg CLIConfig, profile string) error {
	path, err := CLIConfigPathForProfile(profile)
	if err != nil {
		return err

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check ownership of the file shown in the wrapped error and chown it back to the current user (e.g. sudo chown -R $(whoami) ~/.multica)
  2. Verify permissions with ls -l on the path; chmod 600 the file if needed
  3. If MULTICA_TASK_CONFIG_ROOT is set, confirm it points at a directory this user owns and unset it if the daemon context is unintended
  4. If the file is not meant to exist, delete it so the next load takes the clean 'missing file' path instead

Example fix

# before
$ multica config get
read CLI config: open /home/user/.multica/config.json: permission denied

# after
$ sudo chown -R $(whoami) ~/.multica
$ multica config get
Defensive patterns

Strategy: try-catch

Validate before calling

if path, err := cli.CLIConfigPathForProfile(profile); err == nil {
	if info, statErr := os.Stat(path); statErr == nil && info.Mode().Perm()&0o400 != 0o400 {
		// file exists but may not be readable by this user
	}
}

Type guard

func isReadableConfigErr(err error) bool {
	return err != nil && strings.Contains(err.Error(), "read CLI config:")
}

Try / catch

cfg, err := cli.LoadCLIConfigForProfile(profile)
if err != nil {
	if errors.Is(err, os.ErrPermission) {
		// surface an actionable message about ownership/permissions
	}
	return err
}

Prevention

When it happens

Trigger: Calling LoadCLIConfig()/LoadCLIConfigForProfile(profile) when the config file exists but the process lacks read permission (root-owned file after running the CLI with sudo, or MULTICA_TASK_CONFIG_ROOT pointing at a directory chmod'd 0700 owned by another user), or when the path names a directory.

Common situations: Running the CLI once with sudo so ~/.multica/profiles/<name>/config.json becomes root-owned; a task-local root under MULTICA_TASK_CONFIG_ROOT created by a daemon running as a different user; a stray directory named config.json.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/62bd3510354db33a. Report an issue: GitHub.