Skip to content

feat: implement SDK canUseTool interrupt handling#71

Open
BingqingLyu wants to merge 3 commits into
mainfrom
pr-2771-fix-sdk-interrupt
Open

feat: implement SDK canUseTool interrupt handling#71
BingqingLyu wants to merge 3 commits into
mainfrom
pr-2771-fix-sdk-interrupt

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 27, 2026

Copy link
Copy Markdown
Owner

TLDR

Implements interrupt handling for SDK canUseTool responses. When the SDK denies a tool call with interrupt: true, the request is terminated immediately and all pending tools are cascade-cancelled with an INTERRUPTED error type.

Screenshots / Video Demo

N/A — no user-facing change. This is a backend feature for SDK integrations.

Dive Deeper

This PR adds support for the SDK canUseTool interrupt mechanism:

  • New error type: Added ToolErrorType.INTERRUPTED to distinguish interrupted tool calls from regular permission denials (EXECUTION_DENIED).

  • Core scheduler changes (coreToolScheduler.ts):

    • When a tool is denied via canUseTool with cancelMessage, the status is now set to 'error' with INTERRUPTED or EXECUTION_DENIED type instead of 'cancelled'.
    • Added cancelPendingTools() method that cascade-cancels all pending tools (awaiting_approval or scheduled) when an interrupt is triggered. These tools are marked with INTERRUPTED error type.
  • CLI changes:

    • permissionController.ts: Extracts interrupt flag from SDK response and passes it to onConfirm.
    • BaseJsonOutputAdapter.ts: Treats INTERRUPTED errors as permission denials for tracking purposes.
    • nonInteractiveCli.ts: Detects INTERRUPTED responses after tool execution and breaks the loop with an appropriate error message.
  • Interface change: Added optional interrupt?: boolean field to ToolConfirmationPayload to support passing the interrupt flag from SDK responses.

Reviewer Test Plan

  1. SDK Integration Test: Create a custom SDK that implements canUseTool and returns { cancelMessage: 'reason', interrupt: true } when denying a tool.
  2. Verify Interrupt Behavior:
    • Start a multi-turn conversation with multiple pending tool calls
    • Have the SDK deny one tool with interrupt: true
    • Confirm the request terminates immediately and all pending tools are cancelled
  3. Verify Non-Interrupt Denial: Ensure that denials without interrupt: true still work normally (tools can be re-allowed in subsequent turns).
  4. JSON Output Test: Run with --output json and verify INTERRUPTED errors are tracked as permission denials.

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Linked issues / bugs

Mingholy and others added 3 commits April 13, 2026 14:12
Add support for handling interrupt flag from SDK canUseTool callback:

- Add ToolErrorType.INTERRUPTED enum for tracking user-initiated interruptions
- Update ToolConfirmationPayload to include optional interrupt field
- Parse and pass interrupt flag from can_use_tool response in permissionController
- Handle interrupt in coreToolScheduler with proper error type and cascade cancellation
- Update nonInteractiveCli to detect INTERRUPTED errors and break the loop
- Track INTERRUPTED and EXECUTION_DENIED errors in permission_denials
- Fix: add tool responses to history before interrupt to preserve request/response pairs

This enables SDK users to interrupt the conversation flow by returning
{ behavior: 'deny', interrupt: true } from canUseTool callback.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
- Add INTERRUPTED error type for tool denial with interrupt flag
- Update permission controller to parse and pass interrupt field from SDK
- Implement cascade cancellation of pending tools on interrupt
- Add permission_denials field to ResultOptions type
- Add integration tests for canUseTool deny and interrupt behavior
- Fix lint errors in tool-control.test.ts

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
… messages

- Add [PermissionDenied] prefix to permission denial messages for proper distinction
- Propagate original denial message to cascade-cancelled tools in interrupt scenarios
- Clean up test files by removing unnecessary debug options
@BingqingLyu BingqingLyu added conflicting-group-1 conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs and removed conflicting-group-1 labels May 7, 2026
@BingqingLyu

BingqingLyu commented May 7, 2026

Copy link
Copy Markdown
Owner Author

Conflict Group 1

This PR shares modified functions with 9 other PR(s): #112, #113, #114, #117, #14, #17, #6, #75, #88.

These PRs should be reviewed as a batch — merging one may affect the others.

Function File Also modified by
createStreamingInputWithControlPoint permission-control.test.ts #112, #113, #114, #117, #88
runNonInteractive nonInteractiveCli.ts #114, #14, #17, #6, #75
graph LR
    PR71["PR #71"]
    FcreateStreamingInputWithControlPoint_8943["createStreamingInputWithControlPoint<br>permission-control.test.ts"]
    PR71 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    PR112["PR #112"]
    PR112 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    PR113["PR #113"]
    PR113 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    PR114["PR #114"]
    PR114 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    PR117["PR #117"]
    PR117 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    PR88["PR #88"]
    PR88 -->|modifies| FcreateStreamingInputWithControlPoint_8943
    FrunNonInteractive_8467["runNonInteractive<br>nonInteractiveCli.ts"]
    PR71 -->|modifies| FrunNonInteractive_8467
    PR114 -->|modifies| FrunNonInteractive_8467
    PR14["PR #14"]
    PR14 -->|modifies| FrunNonInteractive_8467
    PR17["PR #17"]
    PR17 -->|modifies| FrunNonInteractive_8467
    PR6["PR #6"]
    PR6 -->|modifies| FrunNonInteractive_8467
    PR75["PR #75"]
    PR75 -->|modifies| FrunNonInteractive_8467
Loading

Posted by codegraph-ai conflict detection.

@github-actions

Copy link
Copy Markdown

This pull request has had no activity for 60 days and is being marked as stale. It will be closed in another 30 days if no further activity occurs. To keep it open, push a new commit or leave a comment. Maintainers may apply pinned, status/blocked, status/on-hold, or status/ready-for-merge to exempt it from auto-close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicting-group-1 Conflicting PR group 1 — review as a batch conflicting-pr Shares at least one cross-PR dependency with other PRs status/stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants