From 3dd2e9ddfe7e391f905c282a6f13633721417378 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Fri, 19 Jun 2026 05:21:13 +0000 Subject: [PATCH] =?UTF-8?q?release:=20v0.51.512=20=E2=80=94=20interrupted-?= =?UTF-8?q?turn=20user=20prompt=20no=20longer=20replays=20(#4393,=20#4283)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer fix on top: pass-4 recovered-user keep now uses actual kept neighbours (was forward-scan-only → adjacent-user 400). + 3 regression tests. Codex SAFE + Opus 'ship it' + suite 9575. Co-authored-by: kaishi00 Closes #4393 Closes #4283 --- CHANGELOG.md | 6 ++++++ api/streaming.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 123cff3e..12498491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.512] — 2026-06-19 — Release RW (interrupted-turn user prompt no longer replays) + +### Fixed + +- **A user prompt from an interrupted turn no longer replays in every subsequent request (#4283).** When a turn was interrupted, its recovered user message could be re-sent on later requests (and, in some orphaned-tool shapes, produce two adjacent same-role messages that strict providers reject with a 400). The API-message sanitizer now decides whether to keep a recovered user message based on the actual post-sanitization neighbours — it is kept only when it genuinely separates two assistant turns, and dropped otherwise — with the same logic applied in both the sanitizer and the safe-position mirror. Thanks @kaishi00. + ## [v0.51.511] — 2026-06-19 — Release RV (virtual transcript renders during programmatic scrolls) ### Fixed diff --git a/api/streaming.py b/api/streaming.py index 8d0493ab..7a2486bf 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -3448,7 +3448,7 @@ def _sanitize_messages_for_api(messages, *, cfg: dict = None): # Second pass: build the sanitized list, dropping orphaned tool messages. clean = [] - for idx, msg in enumerate(messages): + for msg in messages: if not isinstance(msg, dict): continue # Skip display-only Thinking entries. They are visible transcript