chenhg5/cc-connect · error

pi: read settings: %w

Error message

pi: read settings: %w

What it means

readSettings successfully resolved the path to pi's settings.json but os.ReadFile failed; the underlying OS error (permission denied, file missing, path is a directory, etc.) is wrapped with %w. This is the adapter's wrapper around any filesystem-level failure while loading pi configuration.

Source

Thrown at agent/pi/pi.go:433

	return filepath.Join(dir, "settings.json")
}

// piSettings represents the structure of pi's settings.json relevant fields.
type piSettings struct {
	EnabledModels  []string `json:"enabledModels"`
	DefaultModel   string   `json:"defaultModel"`
	DefaultProvider string  `json:"defaultProvider"`
}

// readSettings reads and parses pi's settings.json.
func readSettings() (*piSettings, error) {
	path := settingsPath()
	if path == "" {
		return nil, fmt.Errorf("pi: cannot determine settings path")
	}
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("pi: read settings: %w", err)
	}
	var s piSettings
	if err := json.Unmarshal(data, &s); err != nil {
		return nil, fmt.Errorf("pi: parse settings: %w", err)
	}
	return &s, nil
}

// readSettingsModels returns the enabledModels from settings.json as ModelOptions.
func readSettingsModels() ([]core.ModelOption, error) {
	s, err := readSettings()
	if err != nil {
		return nil, err
	}
	if len(s.EnabledModels) == 0 {
		return nil, nil
	}
	models := make([]core.ModelOption, 0, len(s.EnabledModels))

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the wrapped cause with errors.Is(err, fs.ErrNotExist) — if pi was never run, run `pi` once to generate settings.json or create a minimal one manually.
  2. Fix file permissions: chown/chmod ~/.pi/agent/settings.json so the cc-connect process user can read it.
  3. Verify the file is a regular file, not a directory or broken symlink.
  4. If running as a service, confirm it executes as the same user that owns the pi settings.

Example fix

// before
models, err := readSettingsModels()
if err != nil {
    return nil, err
}
// after
models, err := readSettingsModels()
if err != nil {
    if errors.Is(err, fs.ErrNotExist) {
        log.Printf("pi settings.json missing; using built-in defaults")
        return defaultModels, nil
    }
    return nil, fmt.Errorf("load pi models: %w", err)
}
Defensive patterns

Strategy: fallback

Validate before calling

settingsPath := filepath.Join(homeDir, ".pi", "agent", "settings.json")
if _, err := os.Stat(settingsPath); errors.Is(err, fs.ErrNotExist) {
    log.Printf("pi settings.json missing; run pi once to generate it")
}

Try / catch

models, err := readSettingsModels()
if err != nil {
    if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {
        return defaultModels, nil // graceful fallback
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling readSettings (via readSettingsModels or readDefaultModel) when ~/.pi/agent/settings.json does not exist (pi never installed/run), is unreadable due to file permissions, or the path is a directory/locked file so os.ReadFile errors.

Common situations: Fresh machines where pi CLI has never been launched so settings.json was never created; settings.json owned by another user after running pi with sudo; sync tools (Dropbox) leaving placeholder files; disk/permission issues after user migration.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/774af4d5bf370fa5. Report an issue: GitHub.