gastownhall/beads · error · storage.ErrValidation
%w: invalid issue type %s
Error message
%w: invalid issue type %s
What it means
ValidateScalarUpdates returns this when a 'type' update value is the right Go type but is not a recognized issue type: types.IssueType.IsValidWithCustom(customTypes) is false for both built-in and configured custom types. It wraps storage.ErrValidation and names the rejected type in the message.
Source
Thrown at internal/storage/issueops/aggregate.go:126
// 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:
issueType = value
case string:
issueType = types.IssueType(value)
default:
return fmt.Errorf("%w: invalid issue type %v", storage.ErrValidation, rawType)
}
customTypes, err := ResolveCustomTypesInTx(ctx, tx)
if err != nil {
return fmt.Errorf("resolve custom issue types: %w", err)
}
if !issueType.IsValidWithCustom(customTypes) {
return fmt.Errorf("%w: invalid issue type %s", storage.ErrValidation, issueType)
}
}
for _, field := range []string{"assignee", "owner"} {
if raw, ok := updates[field]; ok {
if value, ok := raw.(string); ok {
if err := types.CheckFieldLen(field, value); err != nil {
return err
}
}
}
}
return nil
}
// AuthorizeAssigneeTransferWithPools is the assignee-transfer fence itself,
// with the pool aliases supplied rather than read. It is the single source of
// the predicate: the DBTX path below and the unit-of-work backend, which
// reaches config through a use case rather than a transaction, both evaluateView on GitHub (pinned to 71377f2769)
Solutions
- Use one of the exported types.IssueType constants instead of raw strings
- Register the custom type in the database (or correct the spelling/case) before issuing updates
- Pre-check with issueType.IsValidWithCustom(customTypes) in the caller and surface a friendly message
Example fix
// before
updates["type"] = "Featur" // not valid
// after
t := types.IssueType(strings.ToLower(strings.TrimSpace(input)))
if !t.IsValidWithCustom(customTypes) { return fmt.Errorf("unknown issue type %q", t) }
updates["type"] = t Defensive patterns
Strategy: validation
Validate before calling
ct, err := issueops.ResolveCustomTypesInTx(ctx, tx)
if err != nil { return err }
t := types.IssueType(candidate)
if !t.IsValidWithCustom(ct) {
return fmt.Errorf("unknown issue type %q; valid: %v", t, append(types.AllBuiltin(), ct...))
} Type guard
func knownIssueType(t types.IssueType, custom []types.IssueType) bool {
return t.IsValidWithCustom(custom)
} Try / catch
if err := issueops.ValidateScalarUpdates(ctx, tx, updates); errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "invalid issue type") {
// suggest closest valid type to the user from builtin + custom list
} Prevention
- Expose only builtin type constants plus DB-resolved custom types in UIs
- Normalize case/whitespace on type input before patching
- When copying data between projects, remap type names to the target's valid set first
When it happens
Trigger: updateIssueInTx supplies updates["type"] as a string or types.IssueType whose value is not in the valid builtin set nor in the custom types resolved from the DB.
Common situations: Typo'd type names ('featur' instead of 'feature'); case mismatches ('BUG'); referencing a custom type that was deleted or not yet registered in this database; copying type names between projects with different custom types.
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: invalid persistence mode %q
- %w: invalid issue type %v
- provenance: unknown kind %q
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c8fd0fb175d286ea.
Report an issue: GitHub.