{"record":{"id":"9742e4f2d60e834d","repo":"JuliusBrussee/caveman","slug":"multiple-json-values","errorCode":null,"errorMessage":"multiple JSON values","messagePattern":"multiple JSON values","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/bedrock/usage.go","lineNumber":601,"sourceCode":"\t\tusage.Malformed = true\n\t\treturn\n\t}\n\tdst[snake] = value\n}\n\n// decodeBedrockObject preserves integer lexemes as json.Number. Decoding into\n// float64 first loses precision above 2^53 and could turn an invalid/overflowed\n// provider counter into a plausible billable integer.\nfunc decodeBedrockObject(data []byte, dst *map[string]any) error {\n\tdec := json.NewDecoder(bytes.NewReader(data))\n\tdec.UseNumber()\n\tif err := dec.Decode(dst); err != nil {\n\t\treturn err\n\t}\n\tvar trailing any\n\tif err := dec.Decode(&trailing); err != io.EOF {\n\t\tif err == nil {\n\t\t\treturn fmt.Errorf(\"multiple JSON values\")\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// bedrockUsageObjects returns every usage-bearing sub-object reachable from obj.\n// Bedrock places usage at the top level (\"usage\") for both Converse and\n// InvokeModel; streamed converse emits it under \"metadata\".\nfunc bedrockUsageObjects(obj map[string]any) []map[string]any {\n\tvar out []map[string]any\n\tadd := func(v any) {\n\t\tif m, ok := v.(map[string]any); ok {\n\t\t\tout = append(out, m)\n\t\t}\n\t}\n\tadd(obj[\"usage\"])\n\tif meta, ok := obj[\"metadata\"].(map[string]any); ok {","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/bedrock/usage.go#L583-L619","documentation":"decodeBedrockObject wraps a json.Decoder and rejects payloads that contain more than one JSON value. It decodes the target and then decodes again expecting io.EOF; a successful second decode means trailing data followed the top-level value. The Bedrock proxy layer throws this to guarantee strict single-document payloads before parsing usage data.","triggerScenarios":"Bedrock response/converse payload contains trailing bytes after the top-level JSON object, e.g. two concatenated JSON objects, trailing garbage, or NDJSON-style lines fed into a single-decode path.","commonSituations":"Streaming/NDJSON responses (multiple JSON lines) being passed as one document; upstream providers appending extra data; hand-concatenated JSON in tests or fixtures; chunked body assembly accidentally duplicating a payload.","solutions":["Split NDJSON payloads by newline and decode each line separately before calling decodeBedrockObject","Trim or reject trailing non-whitespace bytes from the response body before decoding","Log the body tail (last ~100 bytes) to identify the extra content source","Verify streaming chunks are not concatenated into one buffer without a decoder per event"],"exampleFix":"// before: one decoder over concatenated NDJSON\nvar obj map[string]any\nerr := decodeBedrockObject(dec, &obj)\n// after: decode each JSON line separately\nfor _, line := range bytes.Split(body, []byte(\"\\n\")) {\n    if len(bytes.TrimSpace(line)) == 0 { continue }\n    dec := json.NewDecoder(bytes.NewReader(line))\n    if err := decodeBedrockObject(dec, &obj); err != nil { return err }\n}","handlingStrategy":"validation","validationCode":"if !json.Valid(bytes.TrimSpace(body)) { return fmt.Errorf(\"payload is not a single JSON document\") }\nif bytes.Contains(bytes.TrimSpace(body)[1:len(body)-1], []byte(\"}{\")) { return fmt.Errorf(\"multiple JSON documents concatenated\") }","typeGuard":null,"tryCatchPattern":"var obj map[string]any\nif err := decodeBedrockObject(dec, &obj); err != nil {\n    if strings.Contains(err.Error(), \"multiple JSON values\") {\n        // split the stream and decode per-document\n    }\n    return err\n}","preventionTips":["Treat streaming/NDJSON responses as event streams, never as one JSON document","Trim trailing whitespace-only bytes, reject other trailing data before decode","Add a fixture test with concatenated JSON to pin the strict behavior"],"tags":["json","go","bedrock","decoding"],"backgroundTag":"json-parse-error","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}