You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin passthrough leaves the client an empty turn when the model echoes a render tag #870
The plugin heads every user and assistant text block with a ref annotation (renderTags=text-only), and the model sometimes echoes that annotation line as its own visible content. Where the echo is the entire visible content of a turn, the tag filter deletes it, the emptied chunk is suppressed, and the client receives a turn with no text and no tool call, which the harness aborts:
✘ upstream stream ended before a completion event; this turn may be incomplete
Releasing the echo instead is not acceptable: #463 keeps a turn that is a pure tag echo swallowed, and #674 plus the degenerate-turn warning exist precisely so that imitated render tags never reach the client. So the turn has to be made usable without leaking the annotation.
Expected: when a turn ends cleanly (stop / end_turn / stop_sequence) with zero visible text and no tool call, the pipe should obtain a usable continuation rather than hand the client an empty turn. Constraints that follow from the existing contracts: at most one retry, the retry must respect the wire's framing (one message_start / message_stop on anthropic, retry block indices offset, the openai terminal being the finish_reason chunk rather than [DONE] so the first attempt's terminal does not arrive twice), and if the retry is also empty the original empty turn must still pass through with the existing [degenerate-turn] warning rather than silently vanishing.
Reviewed #871 as requested (rebased head 8d0a089, onto current master) — no parallel change implemented. Verdict: ready for merge (human-side, per repo rules).
Traced the pipe against every constraint in this issue: one-shot retry gate placed after event processing (coalesced content+finish_reason chunks count their text first), clean-terminal whitelist (stop/end_turn/stop_sequence only), OpenAI terminal = finish_reason chunk so the first attempt's [DONE] never doubles, Anthropic single message_start/message_stop with retry blocks index-offset after the client's own, and retry-also-empty falls back to the original empty turn with the existing [degenerate-turn] warning. All hold; truncation (Plugin mode: upstream stream truncated before terminal event → client gets bare cut-off SSE (no completion/error event) #721) survives takeover too.
Verified locally on the rebased head: typecheck clean, build clean, full suite A/B vs master — delta is exactly the 8 new tests (all passing); the single failing test fails byte-identically on clean master (pre-existing, also explains the red CI test jobs on both sides).
Activity
docs: cite the filed numbers in the plugin empty-turn retry changelog…
joshwapohlmann commentedon Sep 16, 2026
The fix is already filed as PR #871 (branch
2026-09-16_870-plugin-empty-turn-retry). Please review there rather than implementing a parallel change.docs: cite the filed numbers in the plugin empty-turn retry changelog…
fix(plugin): reach the degenerate-turn retry on the Google wire too (r…
Harden the Responses plugin pipe with the degenerate-turn retry (ranx…
Harden the Responses plugin pipe with the degenerate-turn retry (ranx…
Harden the Responses plugin pipe with the degenerate-turn retry (ranx…
ranxianglei commentedon Sep 16, 2026
Reviewed #871 as requested (rebased head
8d0a089, onto current master) — no parallel change implemented. Verdict: ready for merge (human-side, per repo rules).stop/end_turn/stop_sequenceonly), OpenAI terminal = finish_reason chunk so the first attempt's[DONE]never doubles, Anthropic singlemessage_start/message_stopwith retry blocks index-offset after the client's own, and retry-also-empty falls back to the original empty turn with the existing[degenerate-turn]warning. All hold; truncation (Plugin mode: upstream stream truncated before terminal event → client gets bare cut-off SSE (no completion/error event) #721) survives takeover too.中文摘要:按你的要求只做了 review(未另起实现)——#871 逐条满足本 issue 的约束、本地验证无回归(唯一失败项为 master 既有问题),可以合并。
fix(plugin): count a dropped tag's interior as degenerate output (ran…
fix(plugin): emit an in-band error when the retry degenerates too (ra…
ranxianglei commentedon Sep 17, 2026
继续
docs: cite the filed numbers in the plugin empty-turn retry changelog…
Harden the Responses plugin pipe with the degenerate-turn retry (ranx…
fix(plugin): count a dropped tag's interior as degenerate output (ran…
fix(plugin): emit an in-band error when the retry degenerates too (ra…
fix(plugin): never retry a bare or trigger-final empty turn (ranxiang…
Merge pull request #871 from joshwapohlmann/2026-09-16_870-plugin-emp…
fix(loop): swallow a render tag wrapped around the model's turn
fix: count inert pings as zero-side-effect in the truncation-retry ga…
fix(plugin): 终点释放按字节分类 —— 单行中文答案不再被误判为退化轮
fix: classify released refs-run tails as prose in degenerate-turn acc…
escalate the degenerate-turn retry: second anti-echo nudge, bounded a…
fix: retry terminal turns ending in a compression-draft closing tag (#…
fix: anthropic legacy-completion stream error emits a well-formed blo…
fix: skip draft-tail retry when the request itself asks for the summ…
test(issue3905): cover plugin chat/responses lanes like the ranxiangl…