{"record":{"id":"aa42627feb4535c4","repo":"gastownhall/beads","slug":"w-metadata-replacement-is-not-valid-json","errorCode":null,"errorMessage":"%w: metadata replacement is not valid JSON","messagePattern":"%w: metadata replacement is not valid JSON","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/aggregate.go","lineNumber":215,"sourceCode":"\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...)\n\t\tif patch.Merge.Set {\n\t\t\t// A JSON null unmarshals into a nil overlay map, so the merge\n\t\t\t// below would silently accept it as \"change nothing\".\n\t\t\tif strings.TrimSpace(string(patch.Merge.Value)) == \"null\" {\n\t\t\t\treturn nil, false, fmt.Errorf(\"%w: metadata merge must be a JSON object\", storage.ErrValidation)\n\t\t\t}\n\t\t\tmerged, err := storage.MergeMetadataJSON(next, patch.Merge.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, fmt.Errorf(\"%w: metadata merge: %v\", storage.ErrValidation, err)\n\t\t\t}\n\t\t\tnext = merged\n\t\t}\n\t\tif len(patch.Set) > 0 || len(patch.Unset) > 0 {\n\t\t\tvalues := make(map[string]json.RawMessage)\n\t\t\tif len(next) > 0 && string(next) != \"null\" {","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/aggregate.go#L197-L233","documentation":"ApplyMetadataPatch throws this wrapped storage.ErrValidation when patch.Replace.Set is true but the replacement metadata document is not valid JSON. The library requires the entire metadata blob to always be parseable JSON, so it rejects the update before writing.","triggerScenarios":"Calling ApplyMetadataPatch with patch.Replace.Set=true and patch.Replace.Value being invalid JSON (e.g. raw text, truncated JSON, or a value produced by naive string concatenation).","commonSituations":"Shell quoting mangling `--metadata '{\"a\":1'`; templates that interpolate into JSON strings; reading a corrupted metadata blob from a config file; passing an empty value (which the code silently coerces to `{}`) followed by non-JSON text.","solutions":["Run json.Valid on the replacement document in the caller before building the patch.","Marshal a Go map/struct with encoding/json instead of hand-building JSON strings.","Fix shell quoting or file content so the value is a complete, valid JSON object."],"exampleFix":"// before\nreq.MetadataReplace = []byte(`{\"a\": 1`) // truncated\n// after\nv := map[string]any{\"a\": 1}\nb, err := json.Marshal(v)\nif err != nil {\n    return err\n}\nreq.MetadataReplace = b // guaranteed valid JSON","handlingStrategy":"validation","validationCode":"func validReplace(v json.RawMessage) bool {\n    t := strings.TrimSpace(string(v))\n    return t == \"\" || json.Valid(v)\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"not valid JSON\") { /* fix replacement document, no retry */ }","preventionTips":["Produce replacement metadata with json.Marshal, never string concatenation.","Check json.Valid on the replacement before setting patch.Replace.","Beware shell quoting: prefer --metadata-from-file or stdin for complex JSON.","Treat empty replacement as {} deliberately, not accidentally."],"tags":["go","validation","metadata","json"],"backgroundTag":"invalid-json-metadata","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}