{"record":{"id":"785e435fc520efdd","repo":"gravitational/teleport","slug":"error-in-patch-json-w","errorCode":null,"errorMessage":"error in patch JSON: %w","messagePattern":"error in patch JSON: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/accessgraph/apiclient/jsonmerge/jsonmerge.go","lineNumber":45,"sourceCode":"\n// JSONMerge merges patch into data using the object-merge behavior expected by\n// the generated union helpers.\nfunc JSONMerge(data, patch json.RawMessage) (json.RawMessage, error) {\n\tif data == nil {\n\t\tdata = []byte(`{}`)\n\t}\n\tif patch == nil {\n\t\tpatch = []byte(`{}`)\n\t}\n\n\tvar dataValue any\n\tif err := unmarshalJSON(data, &dataValue); err != nil {\n\t\treturn nil, fmt.Errorf(\"error in data JSON: %w\", err)\n\t}\n\n\tvar patchValue any\n\tif err := unmarshalJSON(patch, &patchValue); err != nil {\n\t\treturn nil, fmt.Errorf(\"error in patch JSON: %w\", err)\n\t}\n\n\tmerged, err := json.Marshal(mergeJSON(dataValue, patchValue))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error writing merged JSON: %w\", err)\n\t}\n\treturn merged, nil\n}\n\nfunc unmarshalJSON(data []byte, value any) error {\n\tdecoder := json.NewDecoder(bytes.NewReader(data))\n\tdecoder.UseNumber()\n\treturn decoder.Decode(value)\n}\n\nfunc mergeJSON(data, patch any) any {\n\tpatchObject, ok := patch.(map[string]any)\n\tif !ok {","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/accessgraph/apiclient/jsonmerge/jsonmerge.go#L27-L63","documentation":"JSONMerge validates the `patch` document the same way as the base: it unmarshals the patch into a generic value before merging. If the patch bytes are not valid JSON, the decode error is wrapped and returned and the merge is aborted. Note a nil patch is tolerated (treated as `{}`), so this error only fires on non-empty invalid bytes.","triggerScenarios":"Calling JSONMerge or a MergeAction*Properties wrapper with a `patch` argument containing malformed JSON — truncated update payloads, concatenated JSON objects, or non-JSON bytes from an external system (AWS/Teleport/Azure/GitLab/Okta import pipelines).","commonSituations":"Access Graph sync jobs receiving corrupted update documents from discovered-resource exporters; manual edits to stored patch files; wire/queue messages truncated mid-payload.","solutions":["Validate with json.Valid(patch) before the call; treat empty patches as `{}` (JSONMerge already handles nil).","Read the wrapped decoder error for the byte offset of the syntax problem in the patch.","Fix the exporter/producer that generated the malformed patch document."],"exampleFix":"// before\nmerged, err := jsonmerge.JSONMerge(data, patch)\n// after\nif !json.Valid(patch) { return nil, fmt.Errorf(\"invalid patch JSON\") }\nmerged, err := jsonmerge.JSONMerge(data, patch)","handlingStrategy":"validation","validationCode":"if len(patch) == 0 { patch = []byte(\"{}\") } // nil is tolerated, empty bytes may not be\nif !json.Valid(patch) {\n\treturn nil, fmt.Errorf(\"refusing merge: patch is not valid JSON\")\n}\nmerged, err := jsonmerge.JSONMerge(data, patch)","typeGuard":"func isValidJSON(b []byte) bool { return json.Valid(b) }","tryCatchPattern":"merged, err := jsonmerge.JSONMerge(data, patch)\nif err != nil {\n\tif strings.Contains(err.Error(), \"error in patch JSON\") {\n\t\t// patch corrupt: skip the patch or re-fetch from the exporter\n\t}\n}","preventionTips":["Validate every patch document at the producer boundary (exporters, queue consumers).","Treat empty/whitespace patches as `{}` instead of raw empty byte slices.","Round-trip patches through json.Marshal after generation to guarantee well-formedness."],"tags":["json","accessgraph","merge","validation"],"backgroundTag":"invalid-json-input","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}