AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

RunnableWithFallbacks.Stream()/Astream() Misinterpret Empty Upstream Stream As Failure

RunnableWithFallbacks treats a legitimately empty upstream stream as a failure because it uses bare next()/anext() to peek the first chunk, catching StopIteration as an exception. This triggers unwanted fallbacks or raises a misleading RuntimeError.

highConfidence 95%Langchain-CoreAffected V1.4.9

Origin Analysis

In langchain_core/runnables/fallbacks.py, stream() and astream() call context.run(next, stream) / context.run(anext, stream) without a default sentinel. When the upstream stream emits zero chunks, next()/anext() raise StopIteration/StopAsyncIteration, which are subclasses of Exception and therefore caught by the default exceptions_to_handle=(Exception,). This causes the empty stream to be treated as a failure, triggering fallback substitution or re-raising as RuntimeError (PEP 479).
1. Define a RunnableGenerator that yields nothing (empty stream). 2. Wrap it with with_fallbacks([fallback_runnable]). 3. Call chain.stream({}) and observe fallback output instead of []. 4. Wrap with with_fallbacks([]) and observe RuntimeError: generator raised StopIteration.

Fixing Code Block

Edge Case Audit

This changes default behavior: streams that previously triggered fallback on empty output will now return empty. Users who relied on that fallback may need to adjust logic. The sentinel is a private module-level object; if a runnable yields exactly that object, it would be misinterpreted as empty, but this is extremely unlikely. The fix is safe for concurrent async tasks because object identity is immutable. Rollback: pin the previous langchain-core version or revert this change by removing the sentinel argument, but that reintroduces the bug and the misleading RuntimeError.

Ecosystem Topology