{"record":{"id":"65044ea2f5ba712b","repo":"JuliusBrussee/caveman","slug":"object-not-closed","errorCode":null,"errorMessage":"object not closed","messagePattern":"object not closed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/compressors/toon_encode.go","lineNumber":158,"sourceCode":"\t\t\t\tif !ok {\n\t\t\t\t\treturn toonValue{}, fmt.Errorf(\"object key is %T\", keyTok)\n\t\t\t\t}\n\t\t\t\tif _, duplicate := seen[key]; duplicate {\n\t\t\t\t\treturn toonValue{}, fmt.Errorf(\"duplicate object key %q\", key)\n\t\t\t\t}\n\t\t\t\tseen[key] = struct{}{}\n\t\t\t\tval, err := readJSONTOONValue(dec)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn toonValue{}, err\n\t\t\t\t}\n\t\t\t\tfields = append(fields, toonField{name: key, value: val})\n\t\t\t}\n\t\t\tend, err := dec.Token()\n\t\t\tif err != nil {\n\t\t\t\treturn toonValue{}, err\n\t\t\t}\n\t\t\tif d, ok := end.(json.Delim); !ok || d != '}' {\n\t\t\t\treturn toonValue{}, fmt.Errorf(\"object not closed\")\n\t\t\t}\n\t\t\treturn toonValue{kind: toonObject, obj: fields}, nil\n\t\tcase '[':\n\t\t\tvar arr []toonValue\n\t\t\tfor dec.More() {\n\t\t\t\tval, err := readJSONTOONValue(dec)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn toonValue{}, err\n\t\t\t\t}\n\t\t\t\tarr = append(arr, val)\n\t\t\t}\n\t\t\tend, err := dec.Token()\n\t\t\tif err != nil {\n\t\t\t\treturn toonValue{}, err\n\t\t\t}\n\t\t\tif d, ok := end.(json.Delim); !ok || d != ']' {\n\t\t\t\treturn toonValue{}, fmt.Errorf(\"array not closed\")\n\t\t\t}","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/compressors/toon_encode.go#L140-L176","documentation":"After consuming an object's fields, readJSONTOONValue reads one more token and requires it to be the '}' delimiter; anything else raises \"object not closed\". This enforces well-formed JSON objects so the TOON conversion is lossless and never guesses at boundaries.","triggerScenarios":"In the object branch of readJSONTOONValue, the closing dec.Token() returns a non-Delim token or a Delim other than '}' — e.g. truncated JSON, a stray ']' instead of '}', or a tokenizer that stopped early.","commonSituations":"Truncated JSON from a cut-off pipe or partial file; mismatched braces from string-assembled JSON; feeding JSON5/JSONC (comments, trailing commas) that the strict parser mishandles; a bug in a rewritten parser loop.","solutions":["Validate the input JSON with `jq empty input.json` before running the TOON verb.","Ensure the input stream is complete (check the producer didn't truncate the file/pipe).","Strip comments/trailing commas — feed strict RFC 8259 JSON only.","If you changed the parser, verify token consumption per branch matches the standard decoder's contract."],"exampleFix":"// before: truncated\necho '{\"a\": [1,2' | caveman-engine toon\n// after\necho '{\"a\": [1,2]}' | caveman-engine toon","handlingStrategy":"validation","validationCode":"// strict pre-check with the stdlib\nvar probe any\nif err := json.Unmarshal(input, &probe); err != nil { return err } // catches truncation/mismatched braces","typeGuard":null,"tryCatchPattern":"v, err := parseJSONTOON(input)\nif err != nil && strings.Contains(err.Error(), \"object not closed\") {\n    return fmt.Errorf(\"truncated or malformed JSON object: %w\", err)\n}","preventionTips":["Run `jq empty` on input before the TOON verb","Ensure producers finish writing before the engine reads (flush/close)","Feed strict RFC 8259 JSON — no comments or trailing commas"],"tags":["toon","json","parse-error","fail-closed"],"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"}