cd /news/large-language-models/groq-returned-empty-content-the-bug-… · home topics large-language-models article
[ARTICLE · art-89813] src=dev.to ↗ pub= topic=large-language-models verified=true sentiment=· neutral

Groq Returned Empty Content. The Bug Was Hiding in Reasoning Tokens.

A developer at Jo4 Blog discovered that Groq's gpt-oss-safeguard model was returning empty content for ambiguous pages, causing links to remain in 'preview pending' status. The root cause was that the model's reasoning tokens consumed the entire max_tokens budget, leaving no tokens for the actual content. The fix involved switching to max_completion_tokens, increasing the budget to 1024, and adding a warning when reasoning tokens approach the budget.

read4 min views1 publishedAug 10, 2026

This article was originally published on[Jo4 Blog].

We use Groq's gpt-oss-safeguard

model to classify pages behind freshly created short links. Most pages take a few hundred tokens to score. Some don't. And the ones that don't were silently failing — for weeks — until we noticed the symptom: a small but consistent stream of links stuck in "preview pending" forever.

Here's what we found.

The classifier wraps a single Groq chat completion. Send page text, get back a JSON verdict (safe

, unsafe

, with category codes). For 95% of links, this works in well under a second.

For the other 5%, we'd see this in logs:

WARN  Empty content in Groq response
WARN  Classification failed for shortUrl=xyz123 — preview stays enabled

Empty content. Not a network error, not a rate limit, not malformed JSON. The API returned 200, the choices

array had one entry, and choices[0].message.content

was ""

.

What did those pages have in common? They weren't obvious spam. They weren't obvious safe. They were ambiguous — a wellness blog that mentioned medication dosages, a forum thread about firearms law, a satire site quoting violent rhetoric. The kind of content where a human reviewer would also .

Our first instinct: the model is rate-limited or degraded for hard inputs. We added retries. The empty-content rate didn't budge.

Second guess: we're hitting max_tokens

. We had set it to 200. Maybe ambiguous pages produce longer verdicts. We bumped it to 400. Empty content rate didn't budge.

The clue we kept missing was sitting in the response body itself, in a field we weren't parsing.

Groq's response includes a usage

block, and usage.completion_tokens_details.reasoning_tokens

was the smoking gun:

{
  "choices": [{
    "message": { "content": "" },
    "finish_reason": "length"
  }],
  "usage": {
    "completion_tokens": 200,
    "completion_tokens_details": {
      "reasoning_tokens": 200
    }
  }
}

gpt-oss-safeguard

is a reasoning model. Before emitting a single character of content, it spends completion tokens on internal chain-of-thought. Easy pages spend a few dozen reasoning tokens, then emit a 50-token verdict. Ambiguous pages spend several hundred reasoning tokens — and on those, our 200-token budget was being exhausted inside the reasoning phase, leaving zero tokens for content.

The API obediently returned the response. choices[0].message.content

was ""

because there was nothing left in the budget to write into it. finish_reason

was length

, not stop

— the model didn't decide it was done, the token budget cut it off mid-thought.

We were paying for full inference and getting empty strings.

Three changes:

1. Switch from max_tokens to max_completion_tokens

max_tokens

is deprecated for reasoning models. Use the correct parameter name so the API enforces the limit you mean.2. Raise the budget with headroom. We profiled real ambiguous pages: worst case was ~550 reasoning tokens. We set the budget to 1024 — covers worst case plus content with margin to spare.

static final int MAX_COMPLETION_TOKENS = 1024;

String requestBody = objectMapper.writeValueAsString(Map.of(
    "model", modelName,
    "messages", List.of(
        Map.of("role", "system", "content", SAFETY_POLICY),
        Map.of("role", "user", "content", text)
    ),
    "max_completion_tokens", MAX_COMPLETION_TOKENS,
    "temperature", 0.0
));

3. Parse usage and alert when reasoning tokens approach the budget. This is the part that actually prevents the next regression:

if (reasoningTokens != null
    && reasoningTokens > MAX_COMPLETION_TOKENS * 0.8) {
    log.warn("Classifier reasoning tokens near budget: {}/{} — "
        + "consider raising max_completion_tokens",
        reasoningTokens, MAX_COMPLETION_TOKENS);
}

When reasoning crosses 80% of the budget, we log a warning. The next ambiguous page in that distribution is the one that will trip finish_reason=length

and return empty content. We'd rather raise the budget before users see stuck previews, not after.

We also added the diagnostic to the empty-content branch:

if (rawContent == null || rawContent.isBlank()) {
    String finishReason = firstChoice.getFinishReason();
    log.warn("Empty content (finish_reason={} completion_tokens={} "
        + "reasoning_tokens={})",
        finishReason, completionTokens, reasoningTokens);
    return ClassificationResult.error(
        "Empty classifier output (finish_reason=" + finishReason + ")");
}

So if it ever happens again, the next person debugging it has the answer in the first log line, not after a week of squinting.

max_tokens

is a budget the model spends thinking ""

and a finish_reason: length

that you have to parse to see.finish_reason

is the field that tells you the truth.stop

= model is done. length

= the model wanted to keep going and you didn't let it. Treat them as completely different outcomes.completion_tokens_details.reasoning_tokens

is the leading indicator.max_tokens

was the wrong field name for reasoning models. The API silently honored it anyway, which made the bug subtler. The right field name is max_completion_tokens

.Have you been bitten by an LLM that "succeeded" with no output? What was your tell? Drop it in the comments.

Building jo4.io — a URL shortener with AI-backed content scanning that fails loudly, not silently.

── more in #large-language-models 4 stories · sorted by recency
── more on @groq 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/groq-returned-empty-…] indexed:0 read:4min 2026-08-10 ·