Skip to content

grammar : accept repetitions of exactly MAX_REPETITION_THRESHOLD - #27177

Draft
fjc9 wants to merge 1 commit into
ggml-org:masterfrom
fjc9:fix-repetition-threshold-boundary
Draft

fjc9 wants to merge 1 commit into
ggml-org:masterfrom
fjc9:fix-repetition-threshold-boundary

Conversation

@fjc9

@fjc9 fjc9 commented Aug 16, 2026

Copy link
Copy Markdown

Problem

A grammar repetition of exactly MAX_REPETITION_THRESHOLD (2000) crashes with an uncaught std::runtime_error, while both 1999 and 2001 work:

  • "a"{0,1999} → parses and expands normally
  • "a"{0,2001} → the parse-side clamp in parse_sequence kicks in (max_times > MAX_REPETITION_THRESHOLD → unbounded), so it parses
  • "a"{0,2000} → passes the parse-side check (2000 is not > 2000), then handle_repetitions throws because its guard uses >=:
if (n_prev_rules * total_rules >= MAX_REPETITION_THRESHOLD) {
    throw std::runtime_error(...);
}

The practical impact: any JSON schema with "maxLength": 2000 (a very natural round number to pick) makes json_schema_to_grammar emit .{0,2000} and aborts the process, e.g. via llama-completion -j '<schema>':

libc++abi: terminating due to uncaught exception of type std::runtime_error:
failed to parse grammar

This is the boundary case of #25746, and appears to be an off-by-one left over from the clamp introduced for #27087: the two checks disagree about whether the threshold value itself is allowed.

Fix

Make handle_repetitions agree with the parse-side clamp by using a strict comparison, so the threshold value itself expands normally. Values above the threshold keep their existing behavior (clamped to unbounded at parse time for a single repetition, rejected for nested products).

Tests

Added to tests/test-grammar-parser.cpp:

  • "a"{0,2000} and "a"{2000} must parse (previously failed)
  • ("a"{0,50}){0,50} (nested product 2500 > threshold) must still be rejected

Verified locally on macOS (arm64): the new tests pass, the previously crashing JSON schema ("maxLength": 2000) now generates valid constrained output, and the nested-product rejection still triggers.

🤖 Generated with Claude Code

The clamp in parse_sequence only rewrites repetitions strictly above
MAX_REPETITION_THRESHOLD, but the guard in handle_repetitions rejected
the threshold value itself (>=), so a repetition of exactly {0,2000}
passed parsing and then aborted with an uncaught std::runtime_error.
In practice any JSON schema with "maxLength": 2000 crashed the process.

Align the guard with the parse-side clamp (>) and add regression tests:
{0,2000} and {2000} must parse, nested products above the threshold are
still rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fjc9
fjc9 requested a review from ggerganov as a code owner August 16, 2026 11:56
@github-actions github-actions Bot added the testing Everything test related label Aug 16, 2026
@ggml-gh-bot

ggml-gh-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

Hi @fjc9, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • PR Template not respected: Please respect the template when creating a new pull request. Make sure to fill out all required sections.

  • AI-generated content: While code is allowed to be generated by AI, please write the PR description and commit messages on your own without the help of AI.


Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@ggml-gh-bot ggml-gh-bot Bot added the draft PR will be changed to draft by github-actions bot label Aug 16, 2026
@github-actions
github-actions Bot marked this pull request as draft August 16, 2026 12:00
@github-actions github-actions Bot removed the draft PR will be changed to draft by github-actions bot label Aug 16, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

testing Everything test related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant