{"record":{"id":"9f60bfc1dcb3754f","repo":"gastownhall/beads","slug":"w-update-priority-w","errorCode":null,"errorMessage":"%w: update priority: %w","messagePattern":"%w: update priority: %w","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":87,"sourceCode":"// ValidateUpdateRequest checks mutually exclusive guarded-update options and\n// the canonical field values every backend must reject identically. Backends\n// call it before touching the row so an invalid patch cannot half-apply.\nfunc ValidateUpdateRequest(request publicops.UpdateRequest) error {\n\tif request.Claim && (request.ExpectedAssignee != nil || request.ExpectedStatus != nil) {\n\t\treturn fmt.Errorf(\"%w: claim cannot use expected assignee or status\", storage.ErrValidation)\n\t}\n\tif request.ForceAssigneeTransfer && (request.Claim || !request.Patch.Assignee.Set || request.ExpectedAssignee != nil) {\n\t\treturn fmt.Errorf(\"%w: invalid forced assignee transfer\", storage.ErrValidation)\n\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}","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L69-L105","documentation":"ValidateUpdateRequest wraps types.ValidateIssuePriority failures under this message when the patch sets Priority. Validation runs before any SQL executes so an invalid priority cannot partially apply, and the wrapped chain preserves both storage.ErrValidation and the specific priority error.","triggerScenarios":"ExecuteUpdate with Patch.Priority.Set=true and a value that fails types.ValidateIssuePriority — usually a priority outside the accepted range or an unparsable numeric/string value.","commonSituations":"Parsing priority from CLI flags or JSON where '0' or blank slips through as a sentinel; APIs accepting free-text priorities ('urgent', 'high') instead of the numeric scale; off-by-one values beyond the valid P0–P4 range.","solutions":["Validate/normalize the priority against the accepted range before building the patch","Read the wrapped inner error for the exact rule violated","Coerce string inputs through a whitelist parser into the numeric priority type"],"exampleFix":"// before\npatch.Priority = publicops.SetField[int]{Set: true, Value: p} // p from raw user input\n// after\np, err := strconv.Atoi(rawPriority)\nif err != nil || p < 0 || p > 4 { return fmt.Errorf(\"priority must be 0-4, got %q\", rawPriority) }\npatch.Priority = publicops.SetField[int]{Set: true, Value: p}","handlingStrategy":"validation","validationCode":"if patch.Priority.Set {\n  if err := types.ValidateIssuePriority(patch.Priority.Value); err != nil {\n    return fmt.Errorf(\"pre-check priority: %w\", err)\n  }\n}","typeGuard":"func priorityOK(p int) bool { return types.ValidateIssuePriority(p) == nil }","tryCatchPattern":"if err := issueops.ExecuteUpdate(ctx, tx, req); errors.Is(err, storage.ErrValidation) {\n  // inspect wrapped priority error, correct value, retry once\n}","preventionTips":["Parse priorities with a whitelist (P0–P4) rather than free numeric input","Centralize priority parsing in one helper used by CLI/API/UI","Call types.ValidateIssuePriority before building the patch"],"tags":["go","validation","priority","update","storage"],"backgroundTag":"field-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}