plandex-ai/plandex · error
error reading JSON file: %v
Error message
error reading JSON file: %v
What it means
After the hash check, ModelSettingsCheckLocalChanges reads the model settings JSON file itself. Unlike the hash file, a missing settings file is NOT tolerated here — any os.ReadFile error is wrapped with this message, because the file was already confirmed to exist by the earlier fs.FileExists check.
Source
Thrown at app/cli/lib/model_settings.go:51
hashPath := path + ".hash"
exists, err := fs.FileExists(path)
if err != nil {
return ModelSettingsCheckLocalChangesResult{}, fmt.Errorf("error checking model settings file: %v", err)
}
if !exists {
return ModelSettingsCheckLocalChangesResult{}, nil
}
lastSavedHash, err := os.ReadFile(hashPath)
if err != nil && !os.IsNotExist(err) {
return ModelSettingsCheckLocalChangesResult{}, fmt.Errorf("error reading hash file: %v", err)
}
localJsonData, err := os.ReadFile(path)
if err != nil {
return ModelSettingsCheckLocalChangesResult{}, fmt.Errorf("error reading JSON file: %v", err)
}
var clientModelPackSchemaRoles *shared.ClientModelPackSchemaRoles
err = json.Unmarshal(localJsonData, &clientModelPackSchemaRoles)
if err != nil {
return ModelSettingsCheckLocalChangesResult{}, fmt.Errorf("error unmarshalling JSON file: %v", err)
}
currentHash, err := clientModelPackSchemaRoles.ToModelPackSchemaRoles().Hash()
if err != nil {
return ModelSettingsCheckLocalChangesResult{}, fmt.Errorf("error hashing model pack: %v", err)
}
modelPackSchemaRoles := clientModelPackSchemaRoles.ToModelPackSchemaRoles()
return ModelSettingsCheckLocalChangesResult{
HasLocalChanges: currentHash != string(lastSavedHash),
LocalModelPackSchemaRoles: &modelPackSchemaRoles,View on GitHub (pinned to e2d772072e)
Solutions
- Re-run the check — if it was a delete race, the file will either exist again or be reported as absent.
- Check file permissions (chmod u+r) on the settings file.
- Avoid running file-sync tools that delete/replace the settings file while the CLI is running.
- If the path is a symlink, verify the target is readable.
Example fix
// before: blind read
// after: caller-side existence check to shrink the race window
if _, err := os.Stat(settingsPath); err != nil {
return nil
}
res, err := ModelSettingsCheckLocalChanges(settingsPath) Defensive patterns
Strategy: retry
Validate before calling
if _, err := os.Stat(settingsPath); err != nil {
return nil
}
if f, err := os.Open(settingsPath); err != nil {
return fmt.Errorf("settings file unreadable: %w", err)
} else {
f.Close()
} Try / catch
res, err := ModelSettingsCheckLocalChanges(settingsPath)
if err != nil {
if strings.Contains(err.Error(), "reading JSON file") {
time.Sleep(100 * time.Millisecond)
res, err = ModelSettingsCheckLocalChanges(settingsPath)
}
if err != nil {
return err
}
} Prevention
- Don't run git checkout/sync tools that delete settings files while the CLI runs.
- Keep the settings file chmod u+r for the running user.
- Prefer atomic writes (temp file + rename) for the settings file to avoid partial states.
- Retry once on read errors — transient races resolve themselves.
When it happens
Trigger: os.ReadFile(path) fails after fs.FileExists(path) returned true — a race where the file was deleted between the stat and the read, permission denied, or I/O error.
Common situations: Another process (editor, sync tool, git checkout) deletes/replaces the settings file concurrently; the file exists but was chmod'd unreadable; symlink to an unreadable target.
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
- error loading accounts: %v
- error reading JSON file: %v
- error unmarshalling settings-v2.json: %v
- error reading convo file: %v
- error reading convo files: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/64f10be84c6ba056.
Report an issue: GitHub.