gastownhall/beads · error · storage.ErrValidation
%w: invalid persistence mode %q
Error message
%w: invalid persistence mode %q
What it means
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.
Source
Thrown at internal/storage/issueops/aggregate.go:96
}
patch := request.Patch
if patch.Title.Set {
if err := types.ValidateIssueTitle(patch.Title.Value); err != nil {
return fmt.Errorf("%w: update title: %w", storage.ErrValidation, err)
}
}
if patch.Priority.Set {
if err := types.ValidateIssuePriority(patch.Priority.Value); err != nil {
return fmt.Errorf("%w: update priority: %w", storage.ErrValidation, err)
}
}
if patch.EstimatedMinutes.Set {
if err := types.ValidateIssueEstimatedMinutes(patch.EstimatedMinutes.Value); err != nil {
return fmt.Errorf("%w: update estimated_minutes: %w", storage.ErrValidation, err)
}
}
if patch.Persistence.Set && !patch.Persistence.Value.IsValid() {
return fmt.Errorf("%w: invalid persistence mode %q", storage.ErrValidation, patch.Persistence.Value)
}
return nil
}
// ValidateMetadataPatch checks mutually exclusive metadata edits.
func ValidateMetadataPatch(patch publicops.MetadataPatch) error {
if patch.Replace.Set && (patch.Merge.Set || len(patch.Set) > 0 || len(patch.Unset) > 0) {
return fmt.Errorf("%w: cannot combine metadata replacement with incremental metadata edits", storage.ErrValidation)
}
return nil
}
// ValidateScalarUpdates checks typed scalar values before they reach SQL.
func ValidateScalarUpdates(ctx context.Context, tx DBTX, updates map[string]interface{}) error {
if rawType, ok := updates["issue_type"]; ok {
var issueType types.IssueType
switch value := rawType.(type) {
case types.IssueType:View on GitHub (pinned to 71377f2769)
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)
Example fix
// before
patch.Persistence = publicops.SetField[string]{Set: true, Value: "Persistent"} // wrong case
// after
mode := storage.PersistenceEphemeral // typed constant
if !mode.IsValid() { return fmt.Errorf("bad persistence mode %q", mode) }
patch.Persistence = publicops.SetField[string]{Set: true, Value: mode} Defensive patterns
Strategy: validation
Validate before calling
if patch.Persistence.Set && !patch.Persistence.Value.IsValid() {
return fmt.Errorf("unknown persistence mode %q", patch.Persistence.Value)
} Type guard
func persistenceModeOK(s string) bool { return s != "" && (types)(s).IsValid() == true } // use the actual Persistence type
// prefer: func persistenceModeOK(m Persistence) bool { return m.IsValid() } Try / catch
if err := issueops.ExecuteUpdate(ctx, tx, req); errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "persistence mode") {
// map value to nearest valid typed constant and retry
} Prevention
- 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)
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- ExternalDoltConfig: TLSCert set without TLSKey
- unknown backend %q (want one of: %s)
- %w: update title: %w
- %w: update priority: %w
- %w: update estimated_minutes: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/925cf588b6d34c24.
Report an issue: GitHub.