Skip to content

Turn responses: partial-send and error-message mismatches #166

Description

@QueryPlanner

What

Found during a code-reduction audit of the Telegram message-turn pipeline in telegram/bot.py. Three related bugs where the error message shown to the user can misrepresent what actually happened.

  1. A successful agent turn followed by a failed send shows a generic, misleading error. _run_turn_and_send_response (bot.py:1143) calls _run_user_turn_with_retry then _send_final_response. If the turn succeeds (the agent produced and committed a real response to the ADK session) but _send_final_response subsequently raises a non-parse-failure TelegramApiError or network error, the caller's outer except Exception in _handle_message/handle_scheduled_reminder/_handle_file_upload reports a generic "Sorry, I encountered an error" — the user never sees the real answer and has no way to recover it short of /reset or re-asking, even though the agent's turn was not wasted.
  2. Partial multi-chunk send followed by a later chunk failure. In _send_final_response (bot.py:1280), if send_message succeeds for chunk N but a later chunk raises a non-parse TelegramApiError, the function re-raises after already sending some chunks — the caller's generic exception handler then also sends "Sorry, I encountered an error," producing a confusing partial-real-answer-plus-spurious-error-message sequence in the chat.
  3. A failure inside rewind_empty_model_response masks the more accurate error message. In _run_user_turn_with_retry (bot.py:1076, rewind call at line 1124): if rewind_empty_model_response itself throws (e.g. a transient runner/session error) while recovering from an EmptyModelResponseError, that new exception propagates without being caught by any except EmptyModelResponseError clause upstream, so _handle_message's dedicated "please resend, the model returned an empty response" message never fires — the user gets the generic error text instead, which is less actionable.

Priority

Medium — item 1 is the most user-visible: it can make a real, already-computed answer effectively disappear, with the only recovery being /reset or re-asking. Items 2 and 3 are lower-frequency edge cases (transient network/API failures) but same root cause: outer generic exception handlers can't distinguish "the turn failed" from "the turn succeeded but sending/bookkeeping failed."

Level of Effort

Medium (M) — the fix likely means _run_turn_and_send_response (or its callers) distinguishing exceptions raised during the turn itself from exceptions raised while sending the already-computed response, and giving the latter a distinct, more accurate error message (e.g. "I have a response but couldn't deliver it, try /model or wait a moment" vs. the current generic text). Item 2 additionally needs a decision on whether to suppress the generic error when at least one chunk was already delivered.

Sources

Passing criteria / definition of done

  • A test simulates a successful _run_user_turn_with_retry followed by a _send_final_response failure, and asserts the user-facing error text is distinct from (and more accurate than) the generic "encountered an error processing your message" text — or that the response is retried/recovered instead of lost.
  • A test simulates a multi-chunk response where an early chunk sends successfully and a later chunk raises, and asserts the user does not receive both a partial real answer and a generic failure message with no indication that part of the answer already arrived.
  • A test simulates rewind_empty_model_response raising during retry recovery and asserts the resulting user-facing message still communicates "please resend" rather than the fully generic error text (or documents why the generic text is acceptable here).
  • pytest tests/test_telegram_bot.py -q passes with the new assertions.
  • ruff check, ruff format --check, and mypy src/blacki/telegram/ all pass with no new warnings.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions