plandex-ai/plandex · error
error reading hash file: %v
Error message
error reading hash file: %v
What it means
ModelSettingsCheckLocalChanges reads <path>.hash to get the last-saved hash for comparison. A missing hash file is tolerated (os.IsNotExist is allowed); any other read error is wrapped with this message, meaning local-change detection cannot proceed.
Source
Thrown at app/cli/lib/model_settings.go:46
HasLocalChanges bool
LocalModelPackSchemaRoles *shared.ModelPackSchemaRoles
}
func ModelSettingsCheckLocalChanges(path string) (ModelSettingsCheckLocalChangesResult, error) {
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)
}
View on GitHub (pinned to e2d772072e)
Solutions
- Check permissions on the .hash file from the wrapped error path; chmod to make it readable.
- If a directory occupies the .hash path, remove it (the hash will be regenerated).
- As a last resort, delete the .hash file so the next sync treats the file as unhashed and recreates it.
Example fix
// before
// (no pre-check)
// after: ensure the hash sidecar is readable or absent before calling
if info, err := os.Stat(hashPath); err == nil && info.IsDir() {
os.RemoveAll(hashPath)
} Defensive patterns
Strategy: validation
Validate before calling
if info, err := os.Stat(settingsPath + ".hash"); err == nil && info.IsDir() {
os.RemoveAll(settingsPath + ".hash")
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "reading hash file") && !os.IsNotExist(errors.Unwrap(err)) {
return fmt.Errorf("hash sidecar unreadable at %s.hash — fix perms or delete it to regenerate: %w", settingsPath, err)
}
return err
} Prevention
- Treat a missing .hash file as normal; only fix errors for existing-but-unreadable ones.
- Don't chmod dotfiles/lockfiles with root-only ownership.
- Never create a directory where the .hash file is expected.
- Delete a corrupt .hash sidecar to let the next sync regenerate it.
When it happens
Trigger: os.ReadFile on <settingsPath>.hash fails with a non-NotNotExist error: permission denied, path is a directory, or I/O error on the hash sidecar file.
Common situations: The .hash file exists but is unreadable (owned by another user, or created with restrictive perms); a directory named <file>.hash was created accidentally; EDR/antivirus blocking access to dotfiles.
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 reading hash file: %v
- error writing hash file: %v
- error writing hash file: %v
- error setting auth: %v
- error writing auth: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/be3daf1f51fa7e23.
Report an issue: GitHub.