feat: support "defer" hook decision and ResultMessage.deferred_tool_use#865
feat: support "defer" hook decision and ResultMessage.deferred_tool_use#865sarahdeaton wants to merge 1 commit intomainfrom
Conversation
…result field - Add "defer" to PreToolUseHookSpecificOutput.permissionDecision literal - Add DeferredToolUse dataclass and ResultMessage.deferred_tool_use - Parse deferred_tool_use from CLI result messages - Export DeferredToolUse from package root Brings the Python SDK to parity with the TypeScript SDK for the defer round trip: a PreToolUse hook can return permissionDecision: "defer" to halt the run, and the caller reads the deferred tool call off the result message to decide whether to resume.
There was a problem hiding this comment.
LGTM — small additive type/parser change following the established ResultMessage optional-field pattern, with tests.
Extended reasoning...
Overview
Adds "defer" to the PreToolUseHookSpecificOutput.permissionDecision literal, introduces a DeferredToolUse dataclass, threads it through as an optional ResultMessage.deferred_tool_use field, parses it in message_parser.py, and exports it from the package root. A new parser test covers the present case and the existing absent-fields test is extended.
Security risks
None. This is pure type modeling and pass-through deserialization of CLI JSON output into a dataclass — no auth, crypto, filesystem, network, or permission-evaluation logic is touched. The "defer" literal only widens what hook callbacks may return; the actual permission semantics are enforced by the CLI, not the SDK.
Level of scrutiny
Low. The change is additive and backward compatible (new optional field defaults to None), and follows the exact same pattern used for prior ResultMessage optional-field additions (model_usage, permission_denials, errors, uuid). The parser uses data.get() for the optional lookup and constructs the dataclass inside the existing try/except KeyError block, so malformed payloads surface as MessageParseError consistently with the rest of the result-message handling.
Other factors
No CODEOWNERS file in the repo. No prior reviewer comments on the timeline. Bug hunting found nothing. The PR description explains the one non-obvious design choice (separate DeferredToolUse type vs reusing ToolUseBlock) and it is reasonable. ruff/mypy/pytest reported clean per the test plan.
Summary
Brings the Python SDK to parity with the TypeScript SDK for the PreToolUse
"defer"round trip. The CLI has emitteddeferred_tool_useon result messages since v2.1.89, but the Python SDK was dropping it.PreToolUseHookSpecificOutput.permissionDecisionnow accepts"defer"alongside"allow" | "deny" | "ask"DeferredToolUsedataclass (id,name,input) — mirrors the TSSDKDeferredToolUsetypeResultMessage.deferred_tool_use: DeferredToolUse | None— populated when a PreToolUse hook deferred a tool callmessage_parserextracts the field from CLI result outputDeferredToolUseexported from the package rootDeferredToolUseis intentionally a separate type fromToolUseBlock(despite identical fields) to keepContentBlockisinstance checks unambiguous and match the TS SDK's typing.Test plan
ruff check/ruff format— cleanmypy src/— cleanpytest tests/test_message_parser.py tests/test_types.py— 94 passedtest_parse_result_message_with_deferred_tool_usecovers the parse pathtest_parse_result_message_optional_fields_absentextended to assertdeferred_tool_use is Nonewhen absent