Background: true, stream: true Response can be stuck in queued after a near-instant client disconnect

Summary

When a Response created with background: true, stream: true, store: true has its stream aborted at the very first event (response.created, before generation begins / before the response reaches in_progress), the response is left in queued indefinitely and never proceeds to a terminal state. Neither polling (GET /v1/responses/{id}) nor reconnecting (GET /v1/responses/{id}?stream=true&starting_after=0) revives it; it is not cancelled, it simply sits in queued (observed ≥10 minutes).

A disconnect even ~2 seconds later (still queued, zero output produced), or mid-generation (in_progress), recovers fine and completes. So the stuck state appears only when the disconnect coincides almost exactly with response creation.

This contradicts the documented behavior that a dropped background response “continues running and you can reconnect,” and the polling example which shows background responses progressing to completion asynchronously without a held connection.

Environment

  • API: Responses API (/v1/responses)
  • Model: gpt-5.4
  • Request: background: true, stream: true, store: true (store: true set; required for background per the docs’ Limits #1 — this is the documented resumable-streaming configuration, not a stateless request)

What I’m doing (context)

A multi-stage ingestion pipeline that streams each model call live to the user. The server is serverless, so a process can die mid-call; I want a fresh invocation to reattach to the in-progress response and finish it rather than re-issuing (and re-paying for) the request — exactly the “Streaming a background response” pattern (create with background+stream, reconnect with starting_after).

Reproduction — the failing case

  1. POST /v1/responses with { model, input, background: true, stream: true, store: true }; begin consuming the SSE stream.
  2. The instant the first event (response.created) is received, abort the request (e.g. AbortController.abort() in the stream handler) — before any response.output_text.delta events arrive. The response is queued at this point.
  3. Attempt recovery:
    • Poll GET /v1/responses/{id} every ~2s → stays queued for 2+ minutes; never advances to in_progress.
    • Reconnect GET /v1/responses/{id}?stream=true&starting_after=0hangs with no further events (no terminal event) for 10+ minutes.
  4. The response never becomes cancelled; it remains queued.

(Reproduced reliably across multiple runs when the abort fires synchronously on response.created.)

Contrast — cases that recover correctly

  • Abort ~2s after creation, still queued, 0 output deltas → advances to in_progress (~t+5s) and completed (~t+10s).
  • Abort mid-generation (~160 delta events received, status in_progress) → completed ~5s after abort.
  • Fully consumed stream (no disconnect) → completed in ~5s.

Example response ids that recovered (note ~10-minute background retention, so these may have expired by the time you look — happy to generate fresh ones or share a script):

  • mid-generation abort → completed: resp_0cec8df6418419c9006a208fbf941881928bc8d35fd1420ec9
  • ~2s / queued abort → completed: resp_0c46ae55b0480142006a20923de250819d844f92e359ba7ed8

Expected vs. actual

  • Expected: per “if your connection drops, the response continues running and you can reconnect” and the async polling example, a created background response should proceed to a terminal state regardless of when (or whether) the client disconnects.
  • Actual: a near-instant disconnect at response.created parks the response in queued permanently (≥10 min observed); polling never advances it and a stream=true&starting_after=0 reconnect hangs.

Questions

  1. Is a background: true, stream: true response expected to keep running if the client disconnects at response.created, before it reaches in_progress? Is there a minimum commit/engagement window before background execution is guaranteed?
  2. Should GET /v1/responses/{id} polling eventually advance such a response, or is it permanently stuck once orphaned at creation?
  3. Is ?stream=true&starting_after=0 a valid reconnect cursor, and should reconnecting to a stuck-queued response return/terminate rather than hang indefinitely with no events?
  4. Is this behavior model-dependent?

Happy to share exact request payloads, fresh response ids, or a minimal repro script.

1 Like