Responses API + Structured Outputs (gpt-5.6-luna): garbage tokens (foreign scripts / leaked reasoning) inside string values right before the closing quote — identical request via Chat Completions is clean

Summary

Since migrating structured-output calls from Chat Completions to the Responses API, gpt-5.6-luna intermittently emits degenerate tokens inside
string values, immediately before the closing quote
. The JSON is always schema-valid (strict: true), so the garbage flows straight into the app.
The same model + same prompts + same schema via Chat Completions is clean across all our runs.

Environment

  • Model: gpt-5.6-luna (also default snapshot), direct openai api (no Azure)
  • SDK: openai-node 6.3.0, Node 24.11.0
  • Non-streaming client.responses.parse() — final response object, no delta assembly
  • reasoning: { effort: "low" }, max_output_tokens: 20000, store: false

Request shape

{
  "model": "gpt-5.6-luna",
  "store": false,
  "max_output_tokens": 20000,
  "reasoning": { "effort": "low" },
  "text": {
    "format": {
      "type": "json_schema",
      "name": "IngredientExtraction",
      "strict": true,
      "schema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "missing_info_details": { "anyOf": [ { "type": "string", "maxLength": 250 }, { "type": "null" } ] }
        }
      }
    }
  },
  "input": "…static ~6.5k-token system prompt (food-ingredient extraction) + short user message in Spanish…"
}

Actual output (raw, captured)

Last ~90 chars of output_text, with "status": "completed", "incomplete_details": null (NOT a length cut), usage output 229 / reasoning 109:

...quires_more_info":true,"missing_info_details":"¿Qué alimento es la “mandangota”?ください"}

The expected string is just …“mandangota”? — the ください appears out of nowhere right before the closing quote.

More samples from the same request across runs (only this field shown):

"¿Qué alimento querías decir con “mandangota”? בו"
"¿Qué alimento es la “mandangota”?} 彩神争霸代理ablytyped 天天中彩票投注  (jsontas?)"
"¿Qué alimento es la “mandangota”?}тормош ♀ ♀ ♀ ♀ арадио? Nope. Need valid JSON only. My output has weird? I need final correct. The format wrapper
likely expects JSON object, no markdown. Use only JSON. Ensure health index number. Done. (The previous? I"

That last one (from production) reads like leaked reasoning rambling inside the string until the grammar cut it at exactly maxLength: 250.

Incidence & what we ruled out

  • ~55 repro runs on Responses: ~7% severe (random scripts: Japanese, Hebrew, Han, Cyrillic — always right before a closing quote) and ~half of
    runs show milder junk at the same position (trailing space, or ?.).
  • Reproduces with prompt_cache_options: {"mode":"explicit"} and in implicit mode → not cache-related.
  • Reproduces with and without maxLength on the string → the constraint only caps the runaway.
  • No streaming involved; status is always completed; not a refusal.
  • Chat Completions (chat.completions.parse, response_format json_schema strict), identical everything: 0 anomalies.

Hypothesis

The Responses API constrained decoder seems to mishandle the end-of-string token boundary (e.g. merged tokens like ?" being masked by the grammar),
forcing low-probability continuations — occasionally spiraling into foreign-script tokens or reasoning-like self-talk inside the string.

Ask

  1. Is this a known issue with the GPT-5.6 family’s constrained decoding on the Responses API?
  2. Any recommended mitigation other than falling back to Chat Completions (which loses explicit prompt caching)?

Happy to share full raw request/response captures privately.

I’ve encountered the same exact issue you are describing, please share if you end up finding a solution. :smiling_face_with_tear:

I know that we ran into very similar issues with Gemini and it was because of the model getting confused while trying to handle proper escaping within long text strings inside JSON. I’ve seen it with OpenAI models, but much more rarely. It seems to correlate to the length of those strings inside the JSON. We solved it by moving any long string generation into a separate cycle. So JSON never contains long strings.

Update, since @BaileyGranam asked: in the end we couldn’t find any way to prevent this from the request side. We tried everything with the prompt, the schema and the reasoning settings and nothing worked, this really is an upstream bug in the Responses API’s constrained decoder. Since we didn’t want to move back to Chat Completions and lose explicit prompt caching, what we did instead was put a guardrail around the Responses path:

  1. Deep-trim every string in the parsed object, always. That silently absorbs the mild cases (trailing whitespace or stray punctuation right before the closing quote), which were way more frequent than the severe ones.
  2. For the severe cases, a cheap regex over the raw output_text looking for any script that can’t legitimately show up in our output languages (\p{Script=Han}, Hiragana, Katakana, Hangul, Cyrillic, Hebrew, Arabic, etc.) plus control chars and U+FFFD. One detail: be careful not to block scripts you actually need. We had to leave Greek out of the blocklist because “μg” shows up all the time in nutrition text.
  3. If the regex fires, we discard the response and retry once via Chat Completions with the same model, prompt and schema, which is the path that always came out clean for us. Every trigger gets logged and the whole guardrail sits behind an env var kill-switch, so the day OpenAI fixes it we just turn it off and that’s it.

The downside: when the retry fires you pay the call twice and lose the cache on that request, but at aprox 6% severe incidence that’s still way cheaper for us than moving the whole flow back to Chat Completions. And heads up, the script trick only works for us because our legitimate output is always Latin script (es/en). If your app legitimately emits CJK or Cyrillic you’ll need a different signal, because control chars alone catch fewer cases.

Still waiting for someone at OpenAI to officially acknowledge this. If anyone from the team reads this, we have raw request/response captures to share :slight_smile:

Just jumping in to say that we encountered the same issue with gpt-5.6-luna on chat completions api as well.