{"record":{"id":"db8e117b9433e110","repo":"gastownhall/beads","slug":"w-update-title-w","errorCode":null,"errorMessage":"%w: update title: %w","messagePattern":"%w: update title: %w","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":82,"sourceCode":"\t\t}\n\t}\n\treturn updates\n}\n\n// 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","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L64-L100","documentation":"This error wraps types.ValidateIssueTitle failures when an update patch sets Title. ValidateUpdateRequest validates every set field before the backend touches the row, so a bad title cannot half-apply. The double %w produces a chain of storage.ErrValidation and the underlying title error, both matchable via errors.Is.","triggerScenarios":"ExecuteUpdate with Patch.Title.Set=true and a title value that fails types.ValidateIssueTitle — typically an empty/whitespace title or one exceeding the maximum length.","commonSituations":"User input passed straight from a form/CLI into the patch without trimming or length checks; automated scripts writing programmatically generated titles that end up empty; locale text wider than the column limit.","solutions":["Trim and length-check the title before building the patch; skip setting Title entirely when the value is empty","Read the wrapped inner error message to see the exact title rule violated (length vs emptiness)","Add types.ValidateIssueTitle to your input pipeline as a pre-check"],"exampleFix":"// before\npatch.Title = publicops.SetField[string]{Set: true, Value: strings.TrimSpace(userInput)} // may be empty\n// after\ntitle := strings.TrimSpace(userInput)\nif title == \"\" { return errors.New(\"title required\") }\nif err := types.ValidateIssueTitle(title); err != nil { return err }\npatch.Title = publicops.SetField[string]{Set: true, Value: title}","handlingStrategy":"validation","validationCode":"if patch.Title.Set {\n  if err := types.ValidateIssueTitle(patch.Title.Value); err != nil {\n    return fmt.Errorf(\"pre-check title: %w\", err)\n  }\n}","typeGuard":"func titleOK(s string) bool { return types.ValidateIssueTitle(s) == nil }","tryCatchPattern":"err := issueops.ExecuteUpdate(ctx, tx, req)\nif errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"update title\") {\n  // show user the wrapped title rule and let them re-enter\n}","preventionTips":["Trim and reject empty titles at the input boundary","Enforce max length in the UI before sending the patch","Reuse types.ValidateIssueTitle in your own form validation so rules match exactly"],"tags":["go","validation","title","update","storage"],"backgroundTag":"field-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}