{"record":{"id":"fb9c629ebcc78689","repo":"gastownhall/beads","slug":"metadata-replacement-is-not-valid-json","errorCode":null,"errorMessage":"metadata replacement is not valid JSON","messagePattern":"metadata replacement is not valid JSON","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/issue_operations.go","lineNumber":550,"sourceCode":"\tif err := validateMetadataPatch(request.Patch.Metadata); err != nil {\n\t\treturn validationError(err)\n\t}\n\treturn nil\n}\n\nfunc validateCloseRequest(request publicops.CloseRequest) error {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn validationError(fmt.Errorf(\"close: actor and issue ID must not be empty\"))\n\t}\n\treturn nil\n}\n\nfunc validateMetadataPatch(metadata publicops.MetadataPatch) error {\n\tif metadata.Replace.Set && (metadata.Merge.Set || len(metadata.Set) > 0 || len(metadata.Unset) > 0) {\n\t\treturn fmt.Errorf(\"metadata replacement cannot combine with incremental edits\")\n\t}\n\tif metadata.Replace.Set && len(metadata.Replace.Value) > 0 && !json.Valid(metadata.Replace.Value) {\n\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)","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/issue_operations.go#L532-L568","documentation":"When Replace.Set is true and a non-empty replacement value is supplied, validateMetadataPatch requires it to be valid JSON via json.Valid; otherwise this error is returned. A replacement must be a parseable JSON document.","triggerScenarios":"Calling Update with Patch.Metadata.Replace.Set = true and Replace.Value = []byte that json.Valid rejects (truncated, single quotes, trailing comma, raw Go map formatting, etc.).","commonSituations":"Hand-writing metadata in a CLI flag or YAML/env-injected string, string-concatenated JSON, or []byte(fmt.Sprintf(\"%v\", map)) output that is not valid JSON.","solutions":["Run json.Valid (or json.Marshal the value) before assigning Replace.Value","Use json.Marshal on a Go map/struct to guarantee valid JSON","Fix syntax errors in hand-written JSON (quotes, commas, trailing tokens)","Check errors.Is(err, publicops.ErrValidation) in callers to surface input problems"],"exampleFix":"// before\npatch.Metadata.Replace.Value = []byte(`{priority: high}`)\n// after\nv, _ := json.Marshal(map[string]any{\"priority\": \"high\"})\npatch.Metadata.Replace.Set = true\npatch.Metadata.Replace.Value = v","handlingStrategy":"validation","validationCode":"func validReplacement(v []byte) error {\n    if len(v) > 0 && !json.Valid(v) { return fmt.Errorf(\"replacement is not valid JSON\") }\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        // re-marshal the replacement value and retry once\n    }\n    return err\n}","preventionTips":["Always produce Replace.Value with json.Marshal, never string concatenation","Run json.Valid on any hand-written or templated JSON before use","Avoid fmt.Sprintf(\"%v\", map) — Go map formatting is not JSON","Store metadata templates as valid JSON files, not inline strings"],"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"}