Skip to content

Commit 041877d

Browse files
committed
feat: add stop_task client method
1 parent e7b2f44 commit 041877d

4 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/claude_agent_sdk/_internal/query.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,19 @@ async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None:
595595
}
596596
)
597597

598+
async def stop_task(self, task_id: str) -> None:
599+
"""Stop a running task.
600+
601+
Args:
602+
task_id: The task ID from task_notification events
603+
"""
604+
await self._send_control_request(
605+
{
606+
"subtype": "stop_task",
607+
"task_id": task_id,
608+
}
609+
)
610+
598611
async def stream_input(self, stream: AsyncIterable[dict[str, Any]]) -> None:
599612
"""Stream input messages to transport.
600613

src/claude_agent_sdk/client.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,29 @@ async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None:
352352
raise CLIConnectionError("Not connected. Call connect() first.")
353353
await self._query.toggle_mcp_server(server_name, enabled)
354354

355+
async def stop_task(self, task_id: str) -> None:
356+
"""Stop a running task (only works with streaming mode).
357+
358+
After this resolves, a `task_notification` system message with
359+
status `'stopped'` will be emitted by the CLI in the message stream.
360+
361+
Args:
362+
task_id: The task ID from `task_notification` events.
363+
364+
Example:
365+
```python
366+
async with ClaudeSDKClient() as client:
367+
await client.query("Start a long-running task")
368+
369+
# Listen for task_notification to get task_id, then:
370+
await client.stop_task("task-abc123")
371+
# A task_notification with status 'stopped' will follow
372+
```
373+
"""
374+
if not self._query:
375+
raise CLIConnectionError("Not connected. Call connect() first.")
376+
await self._query.stop_task(task_id)
377+
355378
async def get_mcp_status(self) -> dict[str, Any]:
356379
"""Get current MCP server connection status (only works with streaming mode).
357380

src/claude_agent_sdk/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,11 @@ class SDKControlMcpToggleRequest(TypedDict):
845845
enabled: bool
846846

847847

848+
class SDKControlStopTaskRequest(TypedDict):
849+
subtype: Literal["stop_task"]
850+
task_id: str
851+
852+
848853
class SDKControlRequest(TypedDict):
849854
type: Literal["control_request"]
850855
request_id: str
@@ -858,6 +863,7 @@ class SDKControlRequest(TypedDict):
858863
| SDKControlRewindFilesRequest
859864
| SDKControlMcpReconnectRequest
860865
| SDKControlMcpToggleRequest
866+
| SDKControlStopTaskRequest
861867
)
862868

863869

tests/test_streaming_client.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,51 @@ async def _test():
637637

638638
anyio.run(_test)
639639

640+
def test_stop_task(self):
641+
"""Test stop_task sends correct control request with task_id."""
642+
643+
async def _test():
644+
with patch(
645+
"claude_agent_sdk._internal.transport.subprocess_cli.SubprocessCLITransport"
646+
) as mock_transport_class:
647+
mock_transport = _create_mock_transport_with_control_responses()
648+
mock_transport_class.return_value = mock_transport
649+
650+
async with ClaudeSDKClient() as client:
651+
await client.stop_task("task-abc123")
652+
# Check that a control request was sent via write
653+
write_calls = mock_transport.write.call_args_list
654+
request_found = False
655+
for call in write_calls:
656+
data = call[0][0]
657+
try:
658+
msg = json.loads(data.strip())
659+
req = msg.get("request", {})
660+
if (
661+
msg.get("type") == "control_request"
662+
and req.get("subtype") == "stop_task"
663+
):
664+
assert req.get("task_id") == "task-abc123"
665+
request_found = True
666+
break
667+
except (json.JSONDecodeError, KeyError, AttributeError):
668+
pass
669+
assert request_found, (
670+
"stop_task control request with task_id not found"
671+
)
672+
673+
anyio.run(_test)
674+
675+
def test_stop_task_not_connected(self):
676+
"""Test stop_task when not connected raises error."""
677+
678+
async def _test():
679+
client = ClaudeSDKClient()
680+
with pytest.raises(CLIConnectionError, match="Not connected"):
681+
await client.stop_task("task-abc123")
682+
683+
anyio.run(_test)
684+
640685
def test_client_with_options(self):
641686
"""Test client initialization with options."""
642687

0 commit comments

Comments
 (0)