Repository navigation
fix(responses-continuation): fail closed on a collector-truncated, empty output array - #12460
Merged
diegosouzapw merged 2 commits intoSep 3, 2026
Conversation
…pty output array
Live incident (2026-09-02): a huge/reasoning-heavy response blew past
createStructuredSSECollector's own event-count cap mid-stream. The stored
clientResponse then carries `_truncated: true` and `summary.status:
"in_progress"` (never reached "completed") with a genuinely EMPTY
`summary.output` -- not a bounded array with the existing
`_omniroute_truncated_array` sentinel (that only covers an array capped
mid-array, not a collector that stopped before ever populating output at
all).
resolvePreviousResponseState's existing checks didn't cover this shape: an
empty array is not a truncated-marker array, so it passed through as a
valid (if empty) prior response. The next turn's request then reconstructed
to zero real messages, which the upstream provider rejected outright
("[400]: Input required: specify \"prompt\" or \"messages\""), breaking the
conversation with no client-visible continuation path -- OpenClaw saw a
hard request failure with no way to recover mid-turn.
Measured against a live sample of 40 recent successful (200) Ping
responses: 9 of them (22.5%) carried `_truncated: true`. Not a rare edge
case.
Fix: fail closed (return null, same as every other unresolvable case here)
when the stored clientResponse carries `_truncated: true`, or when the
resolved output array is empty regardless of the flag (belt-and-suspenders
for a collector that truncated without setting it, or any other path that
somehow logs zero output items for a response the client actually
received as real). A response the client received as real always has at
least one output item. Failing closed makes the caller ask the client to
resend full history instead of silently reconstructing an empty one --
exactly like a real previous_response_not_found from OpenAI itself, which
OpenClaw's client already handles gracefully.
Two regression tests added to tests/unit/responses-continuation-store.test.ts,
following the file's existing pattern; both proven RED against unfixed code
(returned the empty-output object instead of null) and GREEN after. Full
responses-continuation-*.test.ts suite: 15/15 passing, no regressions.
This was referenced Sep 2, 2026
isGenuineContinuationTurn (and its parent-link helpers) have no callers in this branch — they belong to cheaper-inference#12448. Leaving them here trips the new-code dead-code gate. Keep only the fail-closed _truncated / empty-output checks this PR is about. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw
merged commit Sep 3, 2026
0019a47
into
cheaper-inference:release/v3.8.51
15 of 16 checks passed
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…pty output array (cheaper-inference#12460) Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem. O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (cheaper-inference#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (cheaper-inference#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva. Obrigado, @hartmark.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Problem This Solves
Ping's conversations kept breaking mid-turn with the upstream provider rejecting the request outright:
[400]: Input required: specify "prompt" or "messages". Traced two example failures directly (both traced back to the same root request via a sharedcorrelation_id): #12448 continuation store correctly resolved aprevious_response_id, but the response it resolved from had a genuinely empty storedoutputarray.Why This Change Was Made
A huge/reasoning-heavy response can blow past
createStructuredSSECollector's own event-count cap mid-stream. The storedclientResponsethen carries_truncated: trueandsummary.status: "in_progress"(never reached"completed") with an emptysummary.output— not a bounded array with the existing_omniroute_truncated_arraysentinel (that only covers an array capped mid-array, not a collector that stopped before populating output at all).resolvePreviousResponseState's existing checks didn't cover this shape — an empty array isn't a truncated-marker array, so it passed through as a valid (if empty) prior response, and the next turn reconstructed to zero real messages.Measured against a live sample of 40 recent successful (200) responses: 9 of them (22.5%) carried
_truncated: true. Not a rare edge case.Fix: fail closed (return
null, same as every other unresolvable case in this function) whenclientResponse._truncated === true, or when the resolvedoutputarray is empty regardless of the flag. This makes the caller ask the client to resend full history — exactly like a realprevious_response_not_foundfrom OpenAI, which OpenClaw's client already handles gracefully.User Impact
Stops a real, frequent (~1 in 4 sampled) cause of mid-conversation hard failures for continuation-mode clients.
Evidence
_truncated: true/ emptyoutputdirectly in the stored artifact.tests/unit/responses-continuation-store.test.ts(existing pattern), both proven RED against unfixed code and GREEN after.responses-continuation-*.test.tssuite: 15/15 passing, no regressions.🤖 Generated with Claude Code