Skip to content

fix(gateway): suppress the duplicated provider-error status copy - #98165

Open
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-72131
Open

liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-72131

Conversation

@liuhao1024

Copy link
Copy Markdown

What does this PR do?

Fixes the double delivery of provider errors to chat surfaces (#72131). A provider failure was surfaced through two gateway channels that produce byte-identical text:

  1. the mid-run status callback — _prepare_gateway_status_message rewrote the raw provider error via _gateway_provider_error_reply and sent it;
  2. the failed turn's final response — _sanitize_gateway_final_response rewrote it to the same user-safe text and sent it again.

For adapters without send_or_update_status, _send_or_update_status_coro falls back to a plain send(), so the transient status becomes a second persistent message. And per ValeryP's analysis on the issue, even Telegram is affected on failed turns: progress-bubble cleanup is opt-in per platform (display.platforms.<platform>.cleanup_progress) and its registration is guarded by and not response.get("failed") ("Failed runs skip cleanup so the bubbles remain as breadcrumbs") — a provider failure is a failed turn, so the surviving bubble is a verbatim copy of the final answer.

The fix suppresses the transient status copy instead of delivering the rewrite: a provider-error-shaped status returns None from _prepare_gateway_status_message. This closes both surfaces with a single stateless change — no turn-scoped dedup state (the scoping problem that sank #72144). The final response still reports the failure with the same sanitized text, so the user still sees exactly one user-safe message per failed turn, and no raw provider body is delivered on either path.

Related Issue

Fixes #72131

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • gateway/run.py — _prepare_gateway_status_message: provider-error-shaped text now returns None (suppressed) instead of being rewritten and delivered; the rewrite previously duplicated the final response's text as a persistent message.
  • tests/gateway/test_telegram_noise_filter.py — replaced test_telegram_status_sanitizes_raw_provider_security_errors (asserted the rewrite was delivered on the status path) with test_chat_gateways_suppress_provider_error_status parametrized over chat platforms, asserting suppression; added test_local_status_keeps_raw_provider_errors pinning that local/CLI raw-text surfaces keep the raw diagnostic stream.

How to Test

  1. python3 -m pytest tests/gateway/test_telegram_noise_filter.py tests/gateway/test_local_model_connection_reply.py tests/gateway/test_compression_progress_notices.py -q — Observed result: 183 passed.
  2. python3 -m pytest tests/gateway/ tests/agent/test_turn_context_overflow_warning.py -q — Observed result: only the pre-existing tests/gateway/relay/test_relay_going_idle.py timing flakes fail; they fail identically on a clean upstream/main checkout (4 failed there), unrelated to this diff.
  3. Behavioral check of the duplication itself: python3 -c "from gateway.run import _prepare_gateway_status_message, _sanitize_gateway_final_response; from gateway.config import Platform; raw='API call failed after 3 retries: HTTP 429'; print(_prepare_gateway_status_message(Platform.TELEGRAM, 'lifecycle', raw)); print(_sanitize_gateway_final_response(Platform.TELEGRAM, raw))" — Observed result: None (no status copy) followed by the single sanitized final reply, i.e. exactly one message per failed turn instead of two.

Checklist

Code

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A (inline comment documents the suppression contract)
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

For New Skills

N/A — not a skill.

A provider failure reached chat surfaces twice: the mid-run status
callback was rewritten to the user-safe reply by
_prepare_gateway_status_message, and the failed turn's final response
was rewritten to the byte-identical text by
_sanitize_gateway_final_response. Adapters without
send_or_update_status fall back to a plain send, so both copies stay;
and failed runs are exempted from progress-bubble cleanup
(not response.get("failed")), so even Telegram keeps the status copy.
Suppress the transient status copy instead — the final response still
reports the failure with the same sanitized text, and nothing raw is
delivered on either path.

Fixes NousResearch#72131
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 29, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related to #47791: both address duplicate provider-error delivery for #72131, but this PR suppresses the transient status copy while #47791 preserves a delivered status and suppresses an equivalent final response.

@liuhao1024

Copy link
Copy Markdown
Author

The Windows-only failure in this run is a known flaky timing test, unrelated to this diff.

Failing test: tests/test_desktop_update_windows_progress.py::test_progress_advances_while_the_orchestrator_blocks — AssertionError: /progress unresponsive until deadline (last error: TimeoutError('timed out')).

Why it's unrelated to this change:

  • This PR only touches gateway/run.py (suppressing the duplicated provider-error status copy) and tests/gateway/test_telegram_noise_filter.py — no shared code path with the Desktop update /progress socket/HTTP chain.
  • The same test failed with the identical /progress unresponsive until deadline signature on an unrelated PR the day before (fix(kanban): coerce foreign timestamp values when reading task/run/event rows #97462, kanban-only diff, run 33212153925), and this test has a documented deflake history on main (6d1284a Aug 19 "deflake the Windows progress self-test", 18a15a4 Aug 20 "survives transient /progress socket stalls") — the transient-stall race can still slip through under runner load.
  • Everything else in the same run is green: Python tests / Run tests, Python tests / e2e, macOS-only tests, and all lints passed; only this deadline-sensitive test timed out.

No rerun attempted since external contributors can't trigger one; happy to rebase if a fresh SHA is preferred.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provider errors delivered twice on adapters without send_or_update_status (mid-run status rewrite + identical final response)

2 participants