{"record":{"id":"f9f236d524d44aea","repo":"gastownhall/beads","slug":"w-w-f9f236","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":200,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\treturn AuthorizeAssigneeTransferWithPools(before, request, pools)\n}\n\n// ApplyMetadataPatch returns the canonical metadata value and whether it changes.\nfunc ApplyMetadataPatch(current json.RawMessage, patch publicops.MetadataPatch) (json.RawMessage, bool, error) {\n\tif !patch.Replace.Set && !patch.Merge.Set && len(patch.Set) == 0 && len(patch.Unset) == 0 {\n\t\treturn current, false, nil\n\t}\n\tsetKeys := make([]string, 0, len(patch.Set))\n\tfor key := range patch.Set {\n\t\tsetKeys = append(setKeys, key)\n\t}\n\tsort.Strings(setKeys)\n\tfor _, key := range setKeys {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn nil, false, fmt.Errorf(\"%w: %w\", storage.ErrValidation, err)\n\t\t}\n\t}\n\tfor _, key := range patch.Unset {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn nil, false, fmt.Errorf(\"%w: %w\", storage.ErrValidation, err)\n\t\t}\n\t}\n\tvar next json.RawMessage\n\tif patch.Replace.Set {\n\t\tnext = append(json.RawMessage(nil), patch.Replace.Value...)\n\t\tif len(next) == 0 {\n\t\t\tnext = json.RawMessage(`{}`)\n\t\t}\n\t\tif !json.Valid(next) {\n\t\t\treturn nil, false, fmt.Errorf(\"%w: metadata replacement is not valid JSON\", storage.ErrValidation)\n\t\t}\n\t} else {\n\t\tnext = append(json.RawMessage(nil), current...)","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L182-L218","documentation":"This error wraps storage.ErrValidation together with the underlying ValidateMetadataKey error, thrown by ApplyMetadataPatch when a key in the metadata patch.Set map fails key validation. It signals that the update request carries a metadata key that violates the storage layer's key rules (e.g. empty, too long, or invalid characters), so the whole update is rejected before touching the database.","triggerScenarios":"Calling ApplyMetadataPatch (via ExecuteUpdate) with patch.Set containing at least one key for which storage.ValidateMetadataKey returns an error.","commonSituations":"Building metadata patches programmatically with empty-string keys; user-supplied JSON keys passed through CLI flags without sanitization; keys containing whitespace or control characters from copied config.","solutions":["Validate each metadata key with storage.ValidateMetadataKey before constructing the patch and drop or fix invalid keys.","Trim and sanitize user-supplied keys (reject empty/whitespace keys) at the CLI/API boundary.","Inspect the wrapped error message to identify the exact offending key and correct it."],"exampleFix":"// before\npatch.Set[rawKey] = json.RawMessage(\"1\") // rawKey may be empty\n// after\nif err := storage.ValidateMetadataKey(rawKey); err != nil {\n    return fmt.Errorf(\"invalid metadata key %q: %w\", rawKey, err)\n}\npatch.Set[rawKey] = json.RawMessage(\"1\")","handlingStrategy":"validation","validationCode":"func validSetKeys(set map[string]json.RawMessage) error {\n    for k := range set {\n        if err := storage.ValidateMetadataKey(k); err != nil {\n            return fmt.Errorf(\"key %q: %w\", k, err)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := errors.Is(err, storage.ErrValidation) { /* surface invalid metadata key to caller, do not retry */ }","preventionTips":["Run storage.ValidateMetadataKey on every key before building a patch.","Trim and reject empty/whitespace keys at the CLI boundary.","Never build keys from unsanitized user input.","Add a unit test covering key validation rules for patch construction."],"tags":["go","validation","metadata"],"backgroundTag":"metadata-key-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}