gastownhall/beads · error
validation failed for issue %s: %w
Error message
validation failed for issue %s: %w
What it means
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.
Source
Thrown at internal/storage/issueops/create.go:558
}
if issue.UpdatedAt.IsZero() {
issue.UpdatedAt = now
} else {
issue.UpdatedAt = issue.UpdatedAt.UTC()
}
// Ensure closed issues have a closed_at timestamp.
if issue.Status == types.StatusClosed && issue.ClosedAt == nil {
maxTime := issue.CreatedAt
if issue.UpdatedAt.After(maxTime) {
maxTime = issue.UpdatedAt
}
closedAt := maxTime.Add(time.Second)
issue.ClosedAt = &closedAt
}
if err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {
return fmt.Errorf("validation failed for issue %s: %w", issue.ID, err)
}
if issue.ContentHash == "" {
issue.ContentHash = issue.ComputeContentHash()
}
return nil
}
// ValidateIssueIDPrefix validates that the issue ID matches the configured prefix
// or any of the allowed_prefixes.
func ValidateIssueIDPrefix(id, prefix, allowedPrefixes string) error {
if strings.HasPrefix(id, prefix+"-") {
return nil
}
if allowedPrefixes != "" {
for _, allowed := range strings.Split(allowedPrefixes, ",") {
allowed = strings.TrimSpace(allowed)
if allowed != "" && strings.HasPrefix(id, allowed+"-") {
return nilView on GitHub (pinned to 71377f2769)
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.
Example fix
// before
issue := &types.Issue{Title: "Fix bug", Status: "in_review"}
_, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts) // fails: unknown status
// after
issue := &types.Issue{Title: "Fix bug", Status: "in_progress"} // or register "in_review" as a custom status
_, err := issueops.CreateIssueInTxWithResult(ctx, tx, issue, opts) Defensive patterns
Strategy: validation
Validate before calling
if err := issue.ValidateWithCustom(customStatuses, customTypes); err != nil {
return fmt.Errorf("issue %s invalid before create: %w", issue.ID, err)
} Type guard
func isValidIssue(issue *types.Issue, statuses, issueTypes []string) bool {
return issue != nil && issue.Title != "" &&
slices.Contains(statuses, issue.Status) &&
slices.Contains(issueTypes, issue.Type)
} Try / catch
if err := createIssue(issue); err != nil {
if strings.Contains(err.Error(), "validation failed for issue") {
log.Warnw("issue rejected by domain validation", "id", issue.ID, "cause", err)
return errSkipRecord
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- no store is open for this workspace
- not found
- db: ChildCounterSQLRepository.NextChildID: parentID must not
- db: DependencySQLRepository.Insert: dep must not be nil
- db: DependencySQLRepository.Insert: IssueID must not be empt
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/facfb8ae0316d163.
Report an issue: GitHub.