Skip to content

fix(responses): backfill output[] when missing, not only when empty#602

Closed
williamjameshandley wants to merge 1 commit into
icebear0828:devfrom
williamjameshandley:fix/non-stream-backfill-missing-output
Closed

fix(responses): backfill output[] when missing, not only when empty#602
williamjameshandley wants to merge 1 commit into
icebear0828:devfrom
williamjameshandley:fix/non-stream-backfill-missing-output

Conversation

@williamjameshandley
Copy link
Copy Markdown

Summary

The non-streaming /v1/responses collector (collectPassthrough in src/routes/responses.ts) backfills response.output from accumulated stream events when the upstream response.completed event omits it. The current guard only matches the empty array case:

if (Array.isArray(resp.output) && resp.output.length === 0) {

But upstream (observed on gpt-5.5 via ChatGPT subscription, 2026-05-27) frequently emits response.completed with the output field missing entirely, not as []. Array.isArray(undefined) returns false, the backfill is skipped, and the final non-stream JSON returned to the client has no output field at all.

Symptom

Clients using the official openai Python SDK get TypeError: 'NoneType' object is not iterable when their code accesses response.output_text — the SDK's @property iterates self.output without a None check:

File "openai/types/responses/response.py", line 315, in output_text
    for output in self.output:
TypeError: 'NoneType' object is not iterable

Fix

One-line: loosen the guard to also fire when output is missing:

-        if (Array.isArray(resp.output) && resp.output.length === 0) {
+        if (!Array.isArray(resp.output) || resp.output.length === 0) {

The body of the branch is unchanged — it only runs when there's no upstream output to copy through, so widening the trigger is safe.

Verification

End-to-end on lovelace (Arch Linux, codex-proxy 2.0.76 with this patch applied):

curl -sS http://localhost:8080/v1/responses \
  -H 'Authorization: Bearer pwd' \
  -H 'Content-Type: application/json' \
  -d '{"model":"gpt-5.5","input":[{"role":"user","content":[{"type":"input_text","text":"Reply ok"}]}],"max_output_tokens":50,"stream":false}'

Before: response JSON has no output field (33 top-level keys, output absent). usage.output_tokens=5 but generated text is dropped.

After: response JSON contains

"output": [{"id":"msg_...","type":"message","status":"completed",
            "content":[{"type":"output_text","text":"ok"}],
            "role":"assistant"}],
"output_text": "ok"

mcp-handley-lab's openai adapter (which calls responses.create(stream=False)) recovers from the 'NoneType' is not iterable crash and returns normal text.

Notes

  • The accompanying Chinese comment also updated to reflect the new condition ("为空" → "为空或缺失").
  • No tests added — this is a one-line guard change in a code path already covered by existing non-stream collect tests; the upstream-shape variation is the trigger, which would need a separate fixture to reproduce.

The non-streaming /v1/responses collector (collectPassthrough) backfills
`response.output` from accumulated `response.output_item.done` events
and `response.output_text.delta` chunks when the upstream
`response.completed` event has `output: []`. The guard:

    if (Array.isArray(resp.output) && resp.output.length === 0) { ... }

only matches the *empty array* case. Upstream now (observed on
gpt-5.5 via ChatGPT subscription, 2026-05-27) frequently omits the
`output` field entirely from `response.completed`, in which case
`Array.isArray(undefined)` is false and the backfill is skipped.

The final non-stream JSON body returned to the client then contains
no `output` field at all. Clients using the openai SDK's `Response`
object hit `TypeError: 'NoneType' object is not iterable` when the
`output_text` property iterates `self.output`.

Loosen the guard to also fire when `output` is missing:

    if (!Array.isArray(resp.output) || resp.output.length === 0) { ... }

The body of the branch is unchanged — it only runs when there's no
upstream output to copy through, so widening the guard is safe.

Verified end-to-end: gpt-5.5 with stream=false now returns
`output: [{...message...}]` and `output_text: "ok"` for a simple
prompt; mcp-handley-lab's openai adapter (which calls
`responses.create(stream=false)`) recovers from
`'NoneType' is not iterable` to normal "ok" reply.
archlinux-github pushed a commit to archlinux/aur that referenced this pull request May 27, 2026
Upstream collectPassthrough only backfills response.output[] from
stream events when it's an empty array. Upstream now (2026-05-27) often
omits the field entirely from response.completed, causing the openai
SDK's output_text property to crash with 'NoneType is not iterable'.

Patched against the v2.0.76 tarball via prepare(). Upstream PR:
icebear0828/codex-proxy#602
@xcqwan
Copy link
Copy Markdown

xcqwan commented May 27, 2026

+1

icebear0828 added a commit that referenced this pull request May 27, 2026
The completed.response.output field may be absent entirely (not just an
empty array) from upstream. Change the guard from
`Array.isArray && length === 0` to `!Array.isArray || length === 0` so
both cases trigger backfill from streamed output_item.done / text deltas.

Add two tests covering the missing-output path.
Addresses #602.
@icebear0828
Copy link
Copy Markdown
Owner

Thanks! The fix and two additional tests covering the missing-output path have been landed on dev (7f6765d). Addresses this PR's change with test coverage.

icebear0828 added a commit that referenced this pull request May 27, 2026
- CHANGELOG: CORS allowlist, Docker arch detection, OS cert store, output backfill
- README/README_EN: add @aeltorio, @williamjameshandley, @FlavienKlr to contributors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants