{"record":{"id":"facfb8ae0316d163","repo":"gastownhall/beads","slug":"validation-failed-for-issue-s-w","errorCode":null,"errorMessage":"validation failed for issue %s: %w","messagePattern":"validation failed for issue (.+?): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/create.go","lineNumber":558,"sourceCode":"\t}\n\tif issue.UpdatedAt.IsZero() {\n\t\tissue.UpdatedAt = now\n\t} else {\n\t\tissue.UpdatedAt = issue.UpdatedAt.UTC()\n\t}\n\n\t// Ensure closed issues have a closed_at timestamp.\n\tif issue.Status == types.StatusClosed && issue.ClosedAt == nil {\n\t\tmaxTime := issue.CreatedAt\n\t\tif issue.UpdatedAt.After(maxTime) {\n\t\t\tmaxTime = issue.UpdatedAt\n\t\t}\n\t\tclosedAt := maxTime.Add(time.Second)\n\t\tissue.ClosedAt = &closedAt\n\t}\n\n\tif err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {\n\t\treturn fmt.Errorf(\"validation failed for issue %s: %w\", issue.ID, err)\n\t}\n\tif issue.ContentHash == \"\" {\n\t\tissue.ContentHash = issue.ComputeContentHash()\n\t}\n\treturn nil\n}\n\n// ValidateIssueIDPrefix validates that the issue ID matches the configured prefix\n// or any of the allowed_prefixes.\nfunc ValidateIssueIDPrefix(id, prefix, allowedPrefixes string) error {\n\tif strings.HasPrefix(id, prefix+\"-\") {\n\t\treturn nil\n\t}\n\tif allowedPrefixes != \"\" {\n\t\tfor _, allowed := range strings.Split(allowedPrefixes, \",\") {\n\t\t\tallowed = strings.TrimSpace(allowed)\n\t\t\tif allowed != \"\" && strings.HasPrefix(id, allowed+\"-\") {\n\t\t\t\treturn nil","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/create.go#L540-L576","documentation":"PrepareIssueForInsert runs the final domain validation on an issue before it is written, including validation against configured custom statuses and custom types via ValidateWithCustom. If the issue violates any schema/domain rule (invalid status, type, priority, missing required fields, bad dates), the underlying validation error is wrapped with the issue ID so the caller knows which record failed. The insert is aborted; nothing is written.","triggerScenarios":"Calling CreateIssueInTxWithResult / PromoteFromEphemeralInTx / PreparePublicCreateRequest with an Issue whose Status or Type is not in the builtin set and not in the customStatuses/customTypes lists passed in, or which otherwise fails Issue.ValidateWithCustom (e.g. empty title, invalid priority).","commonSituations":"Importing JSONL exported from a repo that defined custom statuses/types not configured in the target repo; hand-crafted issue structs with a typo'd status ('oprn' vs 'open'); a repo whose config was changed to remove a custom type still referenced by old data; constructing issues programmatically without setting required fields.","solutions":["Read the wrapped inner error (%w) to see the exact field that failed validation, then fix that field on the issue before inserting.","Pass the repo's custom statuses/types (from config) as customStatuses/customTypes to PrepareIssueForInsert so legitimate custom values validate.","Add the missing custom status/type to the repo configuration (.beads config) if the value is intentionally used.","Run a pre-insert validation (issue.Validate()) on constructed issues to catch problems before entering the transaction."],"exampleFix":"// before\nissue := &types.Issue{Title: \"Fix bug\", Status: \"in_review\"}\n_, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts) // fails: unknown status\n// after\nissue := &types.Issue{Title: \"Fix bug\", Status: \"in_progress\"} // or register \"in_review\" as a custom status\n_, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts)","handlingStrategy":"validation","validationCode":"if err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {\n    return fmt.Errorf(\"issue %s invalid before create: %w\", issue.ID, err)\n}","typeGuard":"func isValidIssue(issue *types.Issue, statuses, issueTypes []string) bool {\n    return issue != nil && issue.Title != \"\" &&\n        slices.Contains(statuses, issue.Status) &&\n        slices.Contains(issueTypes, issue.Type)\n}","tryCatchPattern":"if err := createIssue(issue); err != nil {\n    if strings.Contains(err.Error(), \"validation failed for issue\") {\n        log.Warnw(\"issue rejected by domain validation\", \"id\", issue.ID, \"cause\", err)\n        return errSkipRecord\n    }\n    return err\n}","preventionTips":["Always pass the repo's custom statuses/types to create calls.","Validate issues at construction time, not just at insert time.","Keep custom status/type config in sync with imported data.","Test imports against a staging DB before production."],"tags":["validation","storage","go"],"backgroundTag":"issue-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}