{"record":{"id":"925cf588b6d34c24","repo":"gastownhall/beads","slug":"w-invalid-persistence-mode-q","errorCode":null,"errorMessage":"%w: invalid persistence mode %q","messagePattern":"%w: invalid persistence mode %q","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":96,"sourceCode":"\t}\n\tpatch := request.Patch\n\tif patch.Title.Set {\n\t\tif err := types.ValidateIssueTitle(patch.Title.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: update title: %w\", storage.ErrValidation, err)\n\t\t}\n\t}\n\tif patch.Priority.Set {\n\t\tif err := types.ValidateIssuePriority(patch.Priority.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: update priority: %w\", storage.ErrValidation, err)\n\t\t}\n\t}\n\tif patch.EstimatedMinutes.Set {\n\t\tif err := types.ValidateIssueEstimatedMinutes(patch.EstimatedMinutes.Value); err != nil {\n\t\t\treturn fmt.Errorf(\"%w: update estimated_minutes: %w\", storage.ErrValidation, err)\n\t\t}\n\t}\n\tif patch.Persistence.Set && !patch.Persistence.Value.IsValid() {\n\t\treturn fmt.Errorf(\"%w: invalid persistence mode %q\", storage.ErrValidation, patch.Persistence.Value)\n\t}\n\treturn nil\n}\n\n// ValidateMetadataPatch checks mutually exclusive metadata edits.\nfunc ValidateMetadataPatch(patch publicops.MetadataPatch) error {\n\tif patch.Replace.Set && (patch.Merge.Set || len(patch.Set) > 0 || len(patch.Unset) > 0) {\n\t\treturn fmt.Errorf(\"%w: cannot combine metadata replacement with incremental metadata edits\", storage.ErrValidation)\n\t}\n\treturn nil\n}\n\n// ValidateScalarUpdates checks typed scalar values before they reach SQL.\nfunc ValidateScalarUpdates(ctx context.Context, tx DBTX, updates map[string]interface{}) error {\n\tif rawType, ok := updates[\"issue_type\"]; ok {\n\t\tvar issueType types.IssueType\n\t\tswitch value := rawType.(type) {\n\t\tcase types.IssueType:","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L78-L114","documentation":"ValidateUpdateRequest returns this when the patch sets Persistence but the supplied mode string fails patch.Persistence.Value.IsValid(), meaning it is not one of the recognized persistence modes. The check runs pre-write so an unknown mode never reaches SQL, and the offending value is quoted in the message.","triggerScenarios":"ExecuteUpdate with Patch.Persistence.Set=true and a Persistence.Value that is not a valid mode (typo, wrong case, empty string, or a mode from an older API version).","commonSituations":"Hand-written config or JSON passing 'persistent'/'ephemeral' variants that don't match the canonical enum spelling; upgrading beads where a mode was renamed; programmatic request construction using a raw string instead of the typed constant.","solutions":["Use the exported typed constants for the persistence mode instead of free strings","Call IsValid() on the value before building the patch to confirm it is recognized","Check the quoted value in the message against the current IsValid() implementation for the accepted set (watch case sensitivity and renames across versions)"],"exampleFix":"// before\npatch.Persistence = publicops.SetField[string]{Set: true, Value: \"Persistent\"} // wrong case\n// after\nmode := storage.PersistenceEphemeral // typed constant\nif !mode.IsValid() { return fmt.Errorf(\"bad persistence mode %q\", mode) }\npatch.Persistence = publicops.SetField[string]{Set: true, Value: mode}","handlingStrategy":"validation","validationCode":"if patch.Persistence.Set && !patch.Persistence.Value.IsValid() {\n  return fmt.Errorf(\"unknown persistence mode %q\", patch.Persistence.Value)\n}","typeGuard":"func persistenceModeOK(s string) bool { return s != \"\" && (types)(s).IsValid() == true } // use the actual Persistence type\n// prefer: func persistenceModeOK(m Persistence) bool { return m.IsValid() }","tryCatchPattern":"if err := issueops.ExecuteUpdate(ctx, tx, req); errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"persistence mode\") {\n  // map value to nearest valid typed constant and retry\n}","preventionTips":["Always use exported typed constants, never hand-typed strings","Grep CI for string literals assigned to persistence fields","Re-check valid modes after upgrading the library (renames happen)"],"tags":["go","validation","persistence","enum","update"],"backgroundTag":"invalid-enum-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}