{"record":{"id":"9fb46a2ccd9072d9","repo":"gastownhall/beads","slug":"metadata-value-for-key-q-is-not-valid-json-9fb46a","errorCode":null,"errorMessage":"metadata value for key %q is not valid JSON","messagePattern":"metadata value for key %q is not valid JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/issue_operations.go","lineNumber":568,"sourceCode":"\t\treturn fmt.Errorf(\"metadata replacement is not valid JSON\")\n\t}\n\tif metadata.Merge.Set {\n\t\tvar object map[string]json.RawMessage\n\t\tif len(metadata.Merge.Value) == 0 || json.Unmarshal(metadata.Merge.Value, &object) != nil || object == nil {\n\t\t\treturn fmt.Errorf(\"metadata merge must be a JSON object\")\n\t\t}\n\t}\n\tkeys := make([]string, 0, len(metadata.Set))\n\tfor key := range metadata.Set {\n\t\tkeys = append(keys, key)\n\t}\n\tsort.Strings(keys)\n\tfor _, key := range keys {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !json.Valid(metadata.Set[key]) {\n\t\t\treturn fmt.Errorf(\"metadata value for key %q is not valid JSON\", key)\n\t\t}\n\t}\n\tfor _, key := range metadata.Unset {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateReopenRequest(request publicops.ReopenRequest) error {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn validationError(fmt.Errorf(\"reopen: actor and issue ID must not be empty\"))\n\t}\n\treturn nil\n}\n\nfunc semanticIssueEqual(left, right *types.Issue) bool {","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/issue_operations.go#L550-L586","documentation":"Each key in the Set map must have a value that is valid JSON (json.Valid). This per-key check rejects individual bad values and names the offending key, after ValidateMetadataKey approves the key itself.","triggerScenarios":"Calling Update with Patch.Metadata.Set containing an entry whose json.RawMessage / []byte value is not parseable JSON — e.g. []byte(`high`) instead of []byte(`\"high\"`), or an empty value.","commonSituations":"Users passing raw strings via CLI --set flags without quoting, values built with fmt.Sprintf instead of json.Marshal, or empty strings from unset environment variables.","solutions":["Wrap non-JSON scalar values in quotes or marshal them with json.Marshal","Validate each value with json.Valid before building the patch","Use a helper that converts Go values via json.Marshal rather than raw bytes","Quote string values in shell: --set priority='\"high\"'"],"exampleFix":"// before\npatch.Metadata.Set[\"priority\"] = []byte(`high`)\n// after\nv, _ := json.Marshal(\"high\")\npatch.Metadata.Set[\"priority\"] = v","handlingStrategy":"validation","validationCode":"func validSetValues(set map[string]json.RawMessage) error {\n    for k, v := range set {\n        if err := storage.ValidateMetadataKey(k); err != nil { return err }\n        if !json.Valid(v) { return fmt.Errorf(\"value for %q is not valid JSON\", k) }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := uc.Update(ctx, req); err != nil {\n    if errors.Is(err, publicops.ErrValidation) && strings.Contains(err.Error(), \"valid JSON\") {\n        // extract the offending key from the message, fix its value, retry\n    }\n    return err\n}","preventionTips":["Marshal scalar values with json.Marshal so strings get quoted","For CLI --set input, require properly quoted JSON values (e.g. '\"high\"')","Validate all Set values with json.Valid at patch-build time","Reject empty values early — empty bytes are never valid JSON"],"tags":["validation","metadata-patch","json","go"],"backgroundTag":"invalid-json","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}