{"record":{"id":"cea0e145f80bd12f","repo":"gohugoio/hugo","slug":"error-parsing-json-line-n","errorCode":null,"errorMessage":"Error parsing JSON line\\n","messagePattern":"Error parsing JSON line\\\\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/warpc/genavif/avif.c","lineNumber":86,"sourceCode":"} InputMessage;\n\ntypedef struct\n{\n    Header header;\n    InputData data;\n\n} OutputMessage;\n\n#define MAX_LINE_LENGTH 4096\n\nInputMessage parse_input_message(const char *line)\n{\n    InputMessage msg = {0};\n\n    JSON_Value *root_value = json_parse_string(line);\n    if (root_value == NULL)\n    {\n        fprintf(stderr, \"Error parsing JSON line\\n\");\n        return msg;\n    }\n\n    if (json_value_get_type(root_value) != JSONObject)\n    {\n        fprintf(stderr, \"Error: Line did not parse to a valid JSON object\\n\");\n        json_value_free(root_value);\n        return msg;\n    }\n\n    JSON_Object *root_object = json_value_get_object(root_value);\n\n    JSON_Object *header_object = json_object_get_object(root_object, \"header\");\n    if (header_object != NULL)\n    {\n        msg.header.version = (int)json_object_get_number(header_object, \"version\");\n        msg.header.id = (int)json_object_get_number(header_object, \"id\");\n        const char *command_str = json_object_get_string(header_object, \"command\");","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/internal/warpc/genavif/avif.c#L68-L104","documentation":"Printed by genavif's parse_input_message (avif.c:86) when json_parse_string returns NULL for a line read from stdin. The genavif worker speaks a line-delimited RPC protocol with the Hugo Go host: one JSON object per command followed by a binary blob. This message means the line was syntactically invalid JSON, so parson refused to build any value tree and the function returns an empty InputMessage (zeroed header.command). The worker does not write a response for this path, so the Go side sees silence on stdout paired with this stderr line.","triggerScenarios":"A non-JSON byte sequence arrives on genavif's stdin: a blob leak (binary bytes spilling into the line reader because a previous blob's declared size was wrong), a truncated command line longer than MAX_LINE_LENGTH (4096) split mid-object by fgets, a debug print accidentally wired to the RPC pipe, or a hand-crafted test harness sending plain text. Also fires if the host writes a command without the trailing newline that fgets expects before it returns.","commonSituations":"Protocol desynchronization after a prior short blob read (the most common real cause — the stream is now being interpreted as JSON when it is actually pixel bytes), mismatched genavif binary version where the host speaks a newer envelope, stdin accidentally shared with another writer, or a partial write from the Go side because the pipe was closed early (broken pipe / process killed mid-send).","solutions":["Check the immediately preceding stderr lines and the Go-side warpc logs: this error is almost always a symptom of an earlier stream desync, not the root cause.","Verify every write from the Go host writes exactly one JSON line terminated by '\\n' before the 16-byte blob header, and that blob_size in that header matches the bytes actually sent.","If integrating a new command, confirm the JSON fits within MAX_LINE_LENGTH (4096) — long command/err strings or large frameDurations arrays can exceed it.","Reproduce with strace/truss on the genavif process to capture the exact bytes read on fd 0 at the failing iteration.","Ensure no other code path writes raw bytes to the same stdin pipe."],"exampleFix":"// before (host side): forgot the trailing newline\nfmt.Fprintf(stdin, `{\"header\":{\"command\":\"decode\",\"id\":%d}}`, id)\n\n// after: line-delimited as the worker's fgets expects\nfmt.Fprintf(stdin, \"{\\\"header\\\":{\\\"command\\\":\\\"decode\\\",\\\"id\\\":%d}}\\n\", id)","handlingStrategy":"validation","validationCode":"// Before writing a command line to genavif's stdin, validate it is a JSON object.\nfunc writeCommand(w io.Writer, cmd map[string]any) error {\n    b, err := json.Marshal(cmd)\n    if err != nil { return err }\n    if len(b) > 4096 { return fmt.Errorf(\"command JSON %d bytes exceeds 4096\", len(b)) }\n    line := append(b, '\\n')\n    n, err := w.Write(line)\n    if err != nil || n != len(line) { return fmt.Errorf(\"short write: %d/%d\", n, len(line)) }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always terminate each command JSON with a newline and keep it under 4096 bytes.","Never share the worker's stdin pipe with another writer.","After any blob-read error, respawn the worker instead of reusing the desynced stream.","In tests, assert that every command you send round-trips through json.Marshal successfully."],"tags":["rpc","json","protocol","avif","stdin"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}