Skip to content

Misc. bug: server: invalid GBNF grammar is silently ignored instead of returning HTTP 400 (regression from #17937) #24144

Description

@TBOU

Name and Version

$./llama-cli --version
version: 9350 (08bc21b)
built with GNU 13.3.0 for Linux aarch64

Operating systems

Linux

Which llama.cpp modules do you know to be affected?

libllama (core library)

Command line

Problem description & steps to reproduce

Summary

When an invalid GBNF grammar is passed to the server via the grammar field of
/completion or /chat/completions, the request is not rejected: the server
returns HTTP 200 and generates unconstrained output, with the parse error only
visible in the server logs. The documented behavior
(tools/server/README.md)
is an HTTP 400 response:

{ "error": { "code": 400, "message": "Failed to parse grammar", "type": "invalid_request_error" } }

Note that the json_schema field is unaffected: an invalid schema correctly
returns a 400, because json_schema_to_grammar() throws at request-parsing time.

Steps to reproduce

  1. Start llama-server with any model.
  2. Send a completion request with a syntactically invalid grammar, e.g.:
curl -s http://localhost:8080/completion -d '{
  "prompt": "Answer yes or no:",
  "n_predict": 16,
  "grammar": "root ::= this is (not valid GBNF"
}'
  1. Observed: HTTP 200, normal unconstrained completion; error parsing grammar only appears in the server log.
    Expected: HTTP 400 invalid_request_error, as documented.

Root cause

llama_grammar_parser::parse() catches its own exceptions and returns false
(src/llama-grammar.cpp), so llama_sampler_init_grammar() returns nullptr rather
than throwing. Before #17937, common_sampler_init() had an explicit check:

if (!grmr) {
    return nullptr;
}

which allowed the server to detect the failure and send the 400. That check was
removed in commit 254098a ("common : refactor common_sampler + grammar logic
changes", #17937). Since then common_sampler_init() silently builds the sampler
with .grmr = nullptr, no exception ever reaches the server's try/catch in
launch_slot_with_task() (tools/server/server-context.cpp), and the grammar
constraint is simply dropped.

There is currently no server test covering an invalid grammar (only valid-grammar
tests exist in tools/server/tests/unit/test_chat_completion.py), which is likely
why the regression went unnoticed.

Impact

Clients relying on grammar-constrained output (strict JSON, enums, tool-call
formats…) get no signal that their constraint was dropped — malformed grammars
fail open instead of failing loudly.

bug_llama.cpp_claude_analysis.txt

First Bad Commit

254098a (#17937)

Relevant log output

Logs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions