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
- 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.
- Fix file permissions: chown/chmod ~/.pi/agent/settings.json so the cc-connect process user can read it.
- Verify the file is a regular file, not a directory or broken symlink.
- 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
- Run pi interactively once after install so settings.json exists before daemonized use.
- Keep ~/.pi owned by the same user that runs cc-connect; avoid sudo-running pi.
- Stat the settings file at service startup and log a clear warning if absent.
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
- save config: %w
- read existing Agy hooks %s: %w
- kimi: read sessions dir: %w
- pi: cannot determine settings path
- pi: parse settings: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/774af4d5bf370fa5.
Report an issue: GitHub.