plandex-ai/plandex · error

error reading hash file: %v

Error message

error reading hash file: %v

What it means

CustomModelsCheckLocalChanges reads a companion hash file used to detect local modifications; any read error other than 'file does not exist' (which is tolerated) is wrapped as 'error reading hash file: %v'.

Source

Thrown at app/cli/lib/custom_models.go:121

	}

	localJsonData, err := os.ReadFile(path)
	if err != nil {
		return CustomModelsCheckLocalChangesResult{}, fmt.Errorf("error reading JSON file: %v", err)
	}

	var localClientModelsInput shared.ClientModelsInput
	err = json.Unmarshal(localJsonData, &localClientModelsInput)
	if err != nil {
		return CustomModelsCheckLocalChangesResult{}, fmt.Errorf("error unmarshalling JSON file: %v", err)
	}

	localModelsInput := localClientModelsInput.ToModelsInput()

	lastSavedHash, err := os.ReadFile(hashPath)

	if err != nil && !os.IsNotExist(err) {
		return CustomModelsCheckLocalChangesResult{}, fmt.Errorf("error reading hash file: %v", err)
	}

	currentHash, err := localModelsInput.Hash()
	if err != nil {
		return CustomModelsCheckLocalChangesResult{}, fmt.Errorf("error hashing models: %v", err)
	}

	return CustomModelsCheckLocalChangesResult{
		HasLocalChanges:  currentHash != string(lastSavedHash),
		LocalModelsInput: localModelsInput,
	}, nil
}

func WriteCustomModelsFile(path string, modelsInput *shared.ModelsInput) error {
	err := os.MkdirAll(filepath.Dir(path), 0755)
	if err != nil {
		return fmt.Errorf("error creating directory: %v", err)
	}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Check the wrapped OS error for the exact read failure reason
  2. Fix permissions on the directory containing the hash file
  3. Delete the hash file — missing hash is tolerated and treated as 'local changes', triggering a resync
  4. Rerun the sync flow so the hash file is rewritten correctly

Example fix

// before
lastSavedHash, err := os.ReadFile(hashPath)
if err != nil && !os.IsNotExist(err) {
    return CustomModelsCheckLocalChangesResult{}, fmt.Errorf("error reading hash file: %v", err)
}
// after: caller preflight clears bad hash state
if info, err := os.Stat(hashPath); err == nil && info.IsDir() {
    os.RemoveAll(hashPath) // let sync recreate it
}
Defensive patterns

Strategy: fallback

Validate before calling

if info, err := os.Stat(hashPath); err == nil && info.IsDir() {
    os.RemoveAll(hashPath)
}

Try / catch

res, err := CustomModelsCheckLocalChanges(...)
if err != nil && strings.Contains(err.Error(), "error reading hash file") {
    os.Remove(hashPath) // missing hash is tolerated; treat as local changes
    return CustomModelsCheckLocalChanges(path, hashPath)
}

Prevention

When it happens

Trigger: os.ReadFile(hashPath) fails with a non-IsNotExist error — permission denied on the hash file, hashPath is a directory, or an I/O error while reading the stored hash.

Common situations: Permissions broken on the custom-models state directory; sync tool left the hash path as a directory; disk errors; hash file locked by another process.

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 plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/01213562973b3389. Report an issue: GitHub.