@@ -987,7 +987,7 @@ def capture_stderr(line: str) -> None:
987987
988988 try :
989989 options , transport , effective_model = self ._build_claude_query (
990- user_input , timeout , max_turns , capture_stderr
990+ user_input , timeout , max_turns , capture_stderr , can_stop = should_stop is not None
991991 )
992992 # Set on the state BEFORE the AgentStart emit and any finalize path
993993 # (finalize reads it for cost backfill); stays None if setup crashed.
@@ -1147,6 +1147,7 @@ async def _pump_messages(
11471147 if should_stop is not None and should_stop ():
11481148 state .stopped_early_hit = True
11491149 self ._log .debug ("Cooperative stop requested; ending message loop at this boundary" )
1150+ self ._kill_transport (self ._active_transport )
11501151 break
11511152
11521153 def _build_claude_query (
@@ -1155,11 +1156,14 @@ def _build_claude_query(
11551156 timeout : float | None ,
11561157 max_turns : int | None ,
11571158 stderr_callback : Callable [[str ], None ],
1159+ * ,
1160+ can_stop : bool = False ,
11581161 ) -> tuple [ClaudeAgentOptions , SubprocessCLITransport | None , str | None ]:
1159- """Build the SDK options (+ a timeout-only transport) for one turn.
1162+ """Build the SDK options (+ a killable transport) for one turn.
11601163
1161- ``transport`` is None unless a ``timeout`` is set: it is pre-constructed
1162- only so the watchdog can hard-kill the subprocess. ``effective_model`` may
1164+ ``transport`` is None unless a timeout, a turn cap or a cooperative stop
1165+ (``can_stop``) can end the turn: it is pre-constructed only so the harness
1166+ can hard-kill the subprocess. ``effective_model`` may
11631167 be None on a DirectRoute with no configured model. ``stderr_callback`` is
11641168 wired in here but owned by ``communicate``.
11651169 """
@@ -1226,12 +1230,12 @@ def _build_claude_query(
12261230 # For later inspection: captures every field, defaults included.
12271231 self ._sdk_options_dump = dump_dataclass (options )
12281232
1229- # Pre-constructed only under a timeout or turn cap , to retain the subprocess
1230- # handle for hard-kill. None otherwise, so the SDK uses its own default and
1233+ # Pre-constructed only when the harness may end the turn , to retain the
1234+ # subprocess handle for hard-kill. None otherwise, so the SDK uses its own default and
12311235 # tests can mock query() without a real CLI. Closing the SDK's query()
12321236 # stream does not end the CLI: it never closes the generator it wraps.
12331237 transport : SubprocessCLITransport | None = None
1234- if timeout is not None or max_turns is not None :
1238+ if timeout is not None or max_turns is not None or can_stop :
12351239 transport = SubprocessCLITransport (prompt = user_input , options = options )
12361240
12371241 return options , transport , effective_model
0 commit comments