← Back to PRs

#12687: fix: handle empty LLM stream response with failover

by janckerchen open 2026-02-09 13:47 View on GitHub →
agents stale
## Summary Fixes the issue where "request ended without sending any chunks" error from @mariozechner/pi-ai was shown directly to users without proper handling. **Problem:** - When LLM APIs return empty SSE streams, the raw error was exposed to users - No failover retry was triggered - Known to occur with MiniMax, OpenAI Codex, and Anthropic providers **Solution:** - Added `isEmptyStreamError()` detection function - Classified as "timeout" for failover (transient API issue) - User-friendly message: "The AI service returned an empty response. Please try again." - Applied to both `formatAssistantErrorText()` and `sanitizeUserFacingText()` ## Changes - Modified: [errors.ts](src/agents/pi-embedded-helpers/errors.ts) - Added `isEmptyStreamError()` helper (L661-663) - Updated `classifyFailoverReason()` to trigger failover (L655-657) - Updated `formatAssistantErrorText()` with user message (L390-392) - Updated `sanitizeUserFacingText()` with user message (L448-450) ## Test Plan - [x] Code compiles successfully (`bun run build`) - [x] Verified changes in errors.ts - [ ] Manual testing: Confirm empty stream triggers failover - [ ] Manual testing: Confirm user sees friendly message if all failovers fail 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR adds detection for the pi-ai empty SSE stream error (`"request ended without sending any chunks"`), maps it to a transient failover reason ("timeout"), and rewrites the user-facing text to a friendlier message in both assistant-error formatting and general user-facing sanitization. Key paths touched are `formatAssistantErrorText()` (assistant message errors), `sanitizeUserFacingText()` (generic text cleaning), and `classifyFailoverReason()` (retry/failover classification). <h3>Confidence Score: 4/5</h3> - Mostly safe to merge, but there is a remaining path where the raw empty-stream error can still reach users without being sanitized. - Changes are small and localized, and the failover classification looks consistent with existing timeout handling. However, `sanitizeUserFacingText()` only rewrites the empty-stream message when the text matches ERROR_PREFIX_RE, so prefix-less errors can still leak the raw message (the primary issue being addressed). - src/agents/pi-embedded-helpers/errors.ts <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs