Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

fix(telegram): fall back to result text when streamed response is empty - #556

Open
jhamiltoorion wants to merge 1 commit into
letta-ai:mainfrom
jhamiltoorion:fix/telegram-multi-message-fallback
Open

jhamiltoorion wants to merge 1 commit into
letta-ai:mainfrom
jhamiltoorion:fix/telegram-multi-message-fallback

Conversation

@jhamiltoorion

Copy link
Copy Markdown

Summary

  • When streamed assistant text is empty and no message has been sent yet, fall back to resultText so the user receives a response
  • Fixes a case where Telegram users got silence instead of a reply when a model/provider populated only the result field (not the stream)

Context

Discussed with Ezra in Discord: https://discord.com/channels/1161736243340640419/1428499304485490688/threads/1481375937072336976

Test plan

  • Trigger a tool call with a model that populates result text but not streamed text
  • Confirm the Telegram user receives the response rather than silence

🤖 Generated with Claude Code

When a model/provider streams a tool result but the streamed assistant
text is empty and no message has been sent yet, fall back to the result
text so the user still receives a response.

This fixes the case where Telegram users would receive no message at all
when streamed content was empty despite a valid result being available.

Discussed with Ezra: https://discord.com/channels/1161736243340640419/1428499304485490688/threads/1481375937072336976
@jhamiltoorion

Copy link
Copy Markdown
Author

Via Ezra: Nice. When you open it, reference the log pattern (hasResponse=false after divergence warning with sentAnyMessage=true) -- it'll help Cameron review the context quickly.

In Telegram my agent was only able to send one message each time I sent a prompt, and any additional messages it tried to send after using a skill were never seen. We often argued about who said what!

@just-cameron

Copy link
Copy Markdown
Contributor

Let me take a look, thanks!

@just-cameron

Copy link
Copy Markdown
Contributor

Potential regression in the new fallback block in src/core/bot.ts (around the added if (!sentAnyMessage && response.trim().length === 0)).\n\nCould we gate this with streamMsg.success !== false && !streamMsg.error like the other result fallbacks?\n\nWithout that guard, an error result can still set response = resultText, which makes hasResponse true and can suppress the nothingDelivered retry path (including approval-recovery/error-retry behavior).

@just-cameron just-cameron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Telegram empty-stream fallback is a good idea, but there's a missing guard that needs to be added before this can merge.

Missing error/failure guard before fallback send

The fallback at ~line 1645 in bot.ts fires whenever the stream completes with an empty streamedContent, but it doesn't check whether the stream failed or returned an error. If the stream failed (e.g. streamMsg.success === false or streamMsg.error is set), the bot will send a cheerful "sorry, I had nothing to say" fallback message instead of surfacing the actual error.

Add a guard like:

if (streamMsg.success !== false && !streamMsg.error) {

before the empty-content fallback path. There's already an existing test that validates this protection pattern elsewhere in the streaming logic -- follow the same approach here.

Without this, a failed generation silently gets masked by the fallback message, making debugging much harder for users.

@just-cameron just-cameron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent is right -- when the stream produces no content but the result has text, we should fall back to it. But the implementation needs an error guard.

Without checking success !== false, a failed generation (e.g., LLM error, rate limit) would send the error text as if it were a normal response instead of surfacing it through the error handling path. The existing pattern at the result handling block checks resultMsg.success before acting on empty results -- this fallback should do the same:

if (!sentAnyMessage && !response.trim() && resultText && event.raw.success !== false) {
  response = resultText;
}

This ensures:

  • Successful empty streams get the result fallback
  • Failed generations still hit the error/retry path
  • Approval recovery isn't suppressed by a false-positive hasResponse

Written by Cameron ◯ Letta Code

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants