{"record":{"id":"b780f9091107e5a3","repo":"plandex-ai/plandex","slug":"error-writing-hash-file-v-b780f9","errorCode":null,"errorMessage":"error writing hash file: %v","messagePattern":"error writing hash file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/cli/lib/model_settings.go","lineNumber":111,"sourceCode":"\terr = SaveModelPackRolesHash(path, &modelPackSchemaRoles)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error saving model pack roles hash: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc SaveModelPackRolesHash(basePath string, serverModelPack *shared.ModelPackSchemaRoles) error {\n\thashPath := basePath + \".hash\"\n\n\thash, err := serverModelPack.Hash()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error hashing model pack: %v\", err)\n\t}\n\n\terr = os.WriteFile(hashPath, []byte(hash), 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error writing hash file: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc ApplyModelSettings(path string, originalSettings *shared.PlanSettings) (*shared.PlanSettings, error) {\n\tjsonData, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading JSON file: %v\", err)\n\t}\n\n\tsettings, err := originalSettings.DeepCopy()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error copying settings: %v\", err)\n\t}\n\n\tclientModelPackRoles, err := schema.ValidateModelPackInlineJSON(jsonData)\n\tif err != nil {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/cli/lib/model_settings.go#L93-L129","documentation":"SaveModelPackRolesHash writes the computed hash to <basePath>.hash with os.WriteFile (0644). Failures are wrapped as 'error writing hash file: %v'. The settings JSON itself may already exist, so a failure here leaves the pair out of sync and local-change detection may misbehave.","triggerScenarios":"os.WriteFile(hashPath, ...) fails because the .hash file or its directory is read-only/owned by another user, the disk is full, or a directory exists where the .hash file should be.","commonSituations":"Read-only ~/.plandex mount in CI or containers; concurrent CLI runs racing on the same plan; editors or sync tools (Dropbox) locking files.","solutions":["Fix permissions on the directory containing the settings file: chmod u+rw on the .hash path and its directory","Check the wrapped OS error; free space/quota if ENOSPC or EDQUOT","Delete the stale .hash file if it is a directory or corrupt, then retry the operation","Exclude ~/.plandex from file-sync tools that can lock or replace files"],"exampleFix":"// before\nerror writing hash file: open ~/.plandex/xyz/model-settings.json.hash: read-only file system\n// after\nmount -o remount,rw ~/.plandex  # or choose a writable HOME: export HOME=/writable/dir","handlingStrategy":"validation","validationCode":"hashPath := path + \".hash\"\nif info, err := os.Stat(hashPath); err == nil && info.IsDir() {\n    return fmt.Errorf(\"%s is a directory\", hashPath)\n}\nif f, err := os.OpenFile(hashPath, os.O_WRONLY|os.O_CREATE, 0o644); err != nil {\n    return err\n} else { f.Close() }","typeGuard":null,"tryCatchPattern":"if err := lib.SaveModelPackRolesHash(path, &roles); err != nil {\n    if strings.Contains(err.Error(), \"error writing hash file\") {\n        // fix permissions/mount on hashPath, then retry\n    }\n    return err\n}","preventionTips":["Ensure the plan settings directory is writable, not mounted read-only","Remove any directory or symlink occupying the .hash path","Check disk space before large settings syncs","Avoid file-sync/AV tools locking ~/.plandex files"],"tags":["go","filesystem","hash","permissions"],"backgroundTag":"file-write-permission-denied","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}