{"record":{"id":"f8cd2ff5f0606341","repo":"gastownhall/beads","slug":"w-invalid-issue-type-v","errorCode":null,"errorMessage":"%w: invalid issue type %v","messagePattern":"%w: invalid issue type (.+?)","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":119,"sourceCode":"// ValidateMetadataPatch checks mutually exclusive metadata edits.\nfunc ValidateMetadataPatch(patch publicops.MetadataPatch) error {\n\tif patch.Replace.Set && (patch.Merge.Set || len(patch.Set) > 0 || len(patch.Unset) > 0) {\n\t\treturn fmt.Errorf(\"%w: cannot combine metadata replacement with incremental metadata edits\", storage.ErrValidation)\n\t}\n\treturn nil\n}\n\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}","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L101-L137","documentation":"ValidateScalarUpdates returns this when the 'type' entry in the updates map is neither a types.IssueType nor a string — any other Go type is rejected outright before the value is checked against valid/custom types. It wraps storage.ErrValidation and includes the offending value in the message.","triggerScenarios":"updateIssueInTx passes updates[\"type\"] holding an int, a *string, a custom enum, or another non-string/non-IssueType value, e.g. after JSON unmarshalling numbers into the map.","commonSituations":"JSON payloads with numeric type codes decoded into map[string]interface{} (numbers become float64); refactored callers passing typed wrappers or pointers; ORMs producing non-string column values.","solutions":["Convert the value to a string or types.IssueType before putting it in the updates map","If decoding JSON, decode the type field into a string field rather than interface{}","Check the %v value in the message to identify the unexpected Go type and fix the producer"],"exampleFix":"// before\nupdates[\"type\"] = 3 // int rejected\n// after\nupdates[\"type\"] = types.IssueType(3).String() // or a valid string like \"bug\"","handlingStrategy":"type-guard","validationCode":"raw, ok := updates[\"type\"]\nif ok {\n  switch raw.(type) {\n  case types.IssueType, string:\n  default:\n    return fmt.Errorf(\"type must be string or types.IssueType, got %T\", raw)\n  }\n}","typeGuard":"func issueTypeValue(v interface{}) (types.IssueType, bool) {\n  switch t := v.(type) {\n  case types.IssueType:\n    return t, true\n  case string:\n    return types.IssueType(t), true\n  }\n  return \"\", false\n}","tryCatchPattern":"if err := issueops.ValidateScalarUpdates(ctx, tx, updates); errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"invalid issue type\") {\n  // coerce value to string/IssueType and rebuild updates map\n}","preventionTips":["Decode JSON type fields into typed string fields, not interface{}","Normalize with issueTypeValue() before inserting into the updates map","Add a linter/test that walks updates maps asserting value types"],"tags":["go","validation","issue-type","scalar-updates","type-mismatch"],"backgroundTag":"field-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}