gohugoio/hugo · error

Error: Line did not parse to a valid JSON object\n

Error message

Error: Line did not parse to a valid JSON object\n

What it means

Printed by genavif's parse_input_message (avif.c:92) when json_parse_string succeeded but the top-level JSON value is not an object (e.g. a bare array, string, number, or true/false/null). The worker requires a JSON object so it can call json_object_get_object on it; anything else is treated as a malformed command, the root value is freed, and an empty InputMessage is returned with no output written.

Source

Thrown at internal/warpc/genavif/avif.c:92

} OutputMessage;

#define MAX_LINE_LENGTH 4096

InputMessage parse_input_message(const char *line)
{
    InputMessage msg = {0};

    JSON_Value *root_value = json_parse_string(line);
    if (root_value == NULL)
    {
        fprintf(stderr, "Error parsing JSON line\n");
        return msg;
    }

    if (json_value_get_type(root_value) != JSONObject)
    {
        fprintf(stderr, "Error: Line did not parse to a valid JSON object\n");
        json_value_free(root_value);
        return msg;
    }

    JSON_Object *root_object = json_value_get_object(root_value);

    JSON_Object *header_object = json_object_get_object(root_object, "header");
    if (header_object != NULL)
    {
        msg.header.version = (int)json_object_get_number(header_object, "version");
        msg.header.id = (int)json_object_get_number(header_object, "id");
        const char *command_str = json_object_get_string(header_object, "command");
        if (command_str != NULL)
        {
            strncpy(msg.header.command, command_str, sizeof(msg.header.command) - 1);
            msg.header.command[sizeof(msg.header.command) - 1] = '\0';
        }
        const char *err_str = json_object_get_string(header_object, "err");

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Ensure every command line is a single JSON object with at minimum a `header` object containing `command` and `id`.
  2. Validate on the Go side before writing: parse the line once locally with encoding/json and reject if the root is not a json.Decoder token of type '{'.
  3. Add a regression test in Hugo's warpc tests that asserts a non-object root produces a defined error path rather than silence.
  4. Diff the message envelope against the schema documented in parse_input_message and write_output_message.

Example fix

// before: caller sent a JSON array
[{"header":{"command":"decode"}}]

// after: a single JSON object per line
{"header":{"version":1,"id":42,"command":"decode"},"data":{"params":{}}}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the root JSON token is an object before sending.
func mustBeJSONObject(b []byte) error {
    dec := json.NewDecoder(bytes.NewReader(b))
    tok, err := dec.Token()
    if err != nil { return err }
    if d, ok := tok.(json.Delim); !ok || d != '{' {
        return fmt.Errorf("command root is not a JSON object")
    }
    return nil
}

Prevention

When it happens

Trigger: The host (or a test harness) sends a JSON array of commands, a bare number/string, or a JSON value whose root type is not JSONObject. Also occurs if a framing bug causes two JSON objects to be concatenated on one line and parson parses only the first token in a way that yields a non-object root.

Common situations: Hand-rolled test clients that send `["decode"]` instead of `{"header":{...}}`; a protocol-version mismatch where the host uses a new array-based batch format; JSON producers that emit a top-level scalar under error conditions; or a previous command's leftover bytes making the line start with `]` or `,`.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/dff5e42deaf55d76. Report an issue: GitHub.