1414 ListToolsRequest ,
1515)
1616
17+ from .._errors import ProcessError
1718from ..types import (
1819 PermissionResultAllow ,
1920 PermissionResultDeny ,
@@ -112,6 +113,10 @@ def __init__(
112113
113114 # Track first result for proper stream closure with SDK MCP servers
114115 self ._first_result_event = anyio .Event ()
116+ # Preserve CLI execution error text (from result subtype=error_during_execution)
117+ # so initialize/control callers receive actionable errors instead of generic
118+ # process-exit placeholders.
119+ self ._last_execution_error : str | None = None
115120 self ._stream_close_timeout = (
116121 float (os .environ .get ("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT" , "60000" )) / 1000.0
117122 ) # Convert ms to seconds
@@ -209,6 +214,13 @@ async def _read_messages(self) -> None:
209214 # Track results for proper stream closure
210215 if msg_type == "result" :
211216 self ._first_result_event .set ()
217+ if (
218+ message .get ("subtype" ) == "error_during_execution"
219+ and message .get ("is_error" ) is True
220+ ):
221+ result_text = message .get ("result" )
222+ if isinstance (result_text , str ) and result_text .strip ():
223+ self ._last_execution_error = result_text .strip ()
212224
213225 # Regular SDK messages go to the stream
214226 await self ._message_send .send (message )
@@ -219,13 +231,23 @@ async def _read_messages(self) -> None:
219231 raise # Re-raise to properly handle cancellation
220232 except Exception as e :
221233 logger .error (f"Fatal error in message reader: { e } " )
234+
235+ # If the CLI emitted an explicit execution error result before exiting,
236+ # prefer that actionable message for control waiters (e.g. initialize)
237+ # over generic process-exit placeholders.
238+ pending_error : Exception = e
239+ if isinstance (e , ProcessError ) and self ._last_execution_error :
240+ pending_error = Exception (self ._last_execution_error )
241+
222242 # Signal all pending control requests so they fail fast instead of timing out
223243 for request_id , event in list (self .pending_control_responses .items ()):
224244 if request_id not in self .pending_control_results :
225- self .pending_control_results [request_id ] = e
245+ self .pending_control_results [request_id ] = pending_error
226246 event .set ()
227247 # Put error in stream so iterators can handle it
228- await self ._message_send .send ({"type" : "error" , "error" : str (e )})
248+ await self ._message_send .send (
249+ {"type" : "error" , "error" : str (pending_error )}
250+ )
229251 finally :
230252 # Unblock any waiters (e.g. string-prompt path waiting for first
231253 # result) so they don't stall for the full timeout on early exit.
0 commit comments