{"record":{"id":"dba2645f1185cd0d","repo":"plandex-ai/plandex","slug":"error-saving-model-settings-hash-v","errorCode":null,"errorMessage":"error saving model settings hash: %v","messagePattern":"error saving model settings hash: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/cli/lib/model_settings.go","lineNumber":147,"sourceCode":"\tif err != nil {\n\t\tterm.StopSpinner()\n\t\tcolor.New(color.Bold, term.ColorHiRed).Println(\"🚨 Error validating JSON file\")\n\t\tfmt.Println(err.Error())\n\t\tos.Exit(1)\n\t}\n\tmodelPackRoles := clientModelPackRoles.ToModelPackSchemaRoles()\n\n\tmodelPackSchema := shared.ModelPackSchema{\n\t\tName:                 \"custom\",\n\t\tDescription:          \"Model pack with custom settings\",\n\t\tModelPackSchemaRoles: modelPackRoles,\n\t}\n\tmodelPack := modelPackSchema.ToModelPack()\n\tsettings.SetCustomModelPack(&modelPack)\n\n\terr = SaveModelPackRolesHash(path, &modelPackRoles)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error saving model settings hash: %v\", err)\n\t}\n\n\treturn settings, nil\n}\n\nfunc SaveLatestPlanModelSettingsIfNeeded() (bool, error) {\n\tpath := GetPlanModelSettingsPath(CurrentPlanId)\n\tjsonData, err := os.ReadFile(path)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"error reading JSON file: %v\", err)\n\t}\n\n\tvar clientModelPackSchemaRoles *shared.ClientModelPackSchemaRoles\n\terr = json.Unmarshal(jsonData, &clientModelPackSchemaRoles)\n\tif err != nil {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/cli/lib/model_settings.go#L129-L165","documentation":"After merging the custom model pack into the settings copy, ApplyModelSettings calls SaveModelPackRolesHash to refresh the <path>.hash sidecar so future local-change detection compares against the just-applied pack. Any failure is re-wrapped as 'error saving model settings hash: %v'. At this point validation already passed and settings were updated in memory, but the new settings were not persisted to a hash.","triggerScenarios":"SaveModelPackRolesHash fails from either ModelPackSchemaRoles.Hash() marshaling failure or os.WriteFile on the .hash path failing (permissions, read-only mount, disk full).","commonSituations":"Read-only ~/.plandex after applying settings; hash file owned by root after a sudo-run CLI session; plandex-shared Hash() failure from an incompatible schema version.","solutions":["Fix permissions/ownership on the .hash file and its directory (e.g. chown back to your user if a sudo run created it as root)","Check the wrapped cause; free disk space if ENOSPC","Delete model-settings.json + model-settings.json.hash and re-run the apply/sync flow","Match CLI and plandex-shared versions if the underlying Hash() call failed"],"exampleFix":"// before\n-rw-r--r-- 1 root root model-settings.json.hash  # owned by root after sudo run\n// after\nsudo chown \"$USER:$USER\" ~/.plandex/<planId>/model-settings.json*","handlingStrategy":"try-catch","validationCode":"hashPath := path + \".hash\"\nif f, err := os.OpenFile(hashPath, os.O_WRONLY|os.O_CREATE, 0o644); err != nil {\n    return fmt.Errorf(\"hash path not writable: %w\", err)\n} else { f.Close() }","typeGuard":null,"tryCatchPattern":"updated, err := lib.ApplyModelSettings(path, current)\nif err != nil {\n    if strings.Contains(err.Error(), \"error saving model settings hash\") {\n        // settings updated in memory but hash stale: fix permissions,\n        // delete .json+.hash pair, re-run apply/sync\n    }\n    return err\n}","preventionTips":["Ensure file ownership stays with the interactive user (avoid sudo runs of the CLI)","Keep the settings directory writable throughout apply/sync","After failures, remove both the .json and .hash to reset state","Version-match plandex-shared so Hash() cannot fail on valid packs"],"tags":["go","filesystem","hash","sidecar"],"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"}