{"record":{"id":"c8fd0fb175d286ea","repo":"gastownhall/beads","slug":"w-invalid-issue-type-s","errorCode":null,"errorMessage":"%w: invalid issue type %s","messagePattern":"%w: invalid issue type (.+?)","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":126,"sourceCode":"\n// ValidateScalarUpdates checks typed scalar values before they reach SQL.\nfunc ValidateScalarUpdates(ctx context.Context, tx DBTX, updates map[string]interface{}) error {\n\tif rawType, ok := updates[\"issue_type\"]; ok {\n\t\tvar issueType types.IssueType\n\t\tswitch value := rawType.(type) {\n\t\tcase types.IssueType:\n\t\t\tissueType = value\n\t\tcase string:\n\t\t\tissueType = types.IssueType(value)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"%w: invalid issue type %v\", storage.ErrValidation, rawType)\n\t\t}\n\t\tcustomTypes, err := ResolveCustomTypesInTx(ctx, tx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"resolve custom issue types: %w\", err)\n\t\t}\n\t\tif !issueType.IsValidWithCustom(customTypes) {\n\t\t\treturn fmt.Errorf(\"%w: invalid issue type %s\", storage.ErrValidation, issueType)\n\t\t}\n\t}\n\tfor _, field := range []string{\"assignee\", \"owner\"} {\n\t\tif raw, ok := updates[field]; ok {\n\t\t\tif value, ok := raw.(string); ok {\n\t\t\t\tif err := types.CheckFieldLen(field, value); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// AuthorizeAssigneeTransferWithPools is the assignee-transfer fence itself,\n// with the pool aliases supplied rather than read. It is the single source of\n// the predicate: the DBTX path below and the unit-of-work backend, which\n// reaches config through a use case rather than a transaction, both evaluate","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L108-L144","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nupdates[\"type\"] = \"Featur\" // not valid\n// after\nt := types.IssueType(strings.ToLower(strings.TrimSpace(input)))\nif !t.IsValidWithCustom(customTypes) { return fmt.Errorf(\"unknown issue type %q\", t) }\nupdates[\"type\"] = t","handlingStrategy":"validation","validationCode":"ct, err := issueops.ResolveCustomTypesInTx(ctx, tx)\nif err != nil { return err }\nt := types.IssueType(candidate)\nif !t.IsValidWithCustom(ct) {\n  return fmt.Errorf(\"unknown issue type %q; valid: %v\", t, append(types.AllBuiltin(), ct...))\n}","typeGuard":"func knownIssueType(t types.IssueType, custom []types.IssueType) bool {\n  return t.IsValidWithCustom(custom)\n}","tryCatchPattern":"if err := issueops.ValidateScalarUpdates(ctx, tx, updates); errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"invalid issue type\") {\n  // suggest closest valid type to the user from builtin + custom list\n}","preventionTips":["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"],"tags":["go","validation","issue-type","enum","custom-types"],"backgroundTag":"invalid-enum-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}