Skip to content

feat: support "defer" hook decision and ResultMessage.deferred_tool_use#865

Open
sarahdeaton wants to merge 1 commit intomainfrom
feat/defer-hook-decision
Open

feat: support "defer" hook decision and ResultMessage.deferred_tool_use#865
sarahdeaton wants to merge 1 commit intomainfrom
feat/defer-hook-decision

Conversation

@sarahdeaton
Copy link
Copy Markdown
Contributor

Summary

Brings the Python SDK to parity with the TypeScript SDK for the PreToolUse "defer" round trip. The CLI has emitted deferred_tool_use on result messages since v2.1.89, but the Python SDK was dropping it.

  • PreToolUseHookSpecificOutput.permissionDecision now accepts "defer" alongside "allow" | "deny" | "ask"
  • New DeferredToolUse dataclass (id, name, input) — mirrors the TS SDKDeferredToolUse type
  • ResultMessage.deferred_tool_use: DeferredToolUse | None — populated when a PreToolUse hook deferred a tool call
  • message_parser extracts the field from CLI result output
  • DeferredToolUse exported from the package root

DeferredToolUse is intentionally a separate type from ToolUseBlock (despite identical fields) to keep ContentBlock isinstance checks unambiguous and match the TS SDK's typing.

Test plan

  • ruff check / ruff format — clean
  • mypy src/ — clean
  • pytest tests/test_message_parser.py tests/test_types.py — 94 passed
  • New test test_parse_result_message_with_deferred_tool_use covers the parse path
  • Existing test_parse_result_message_optional_fields_absent extended to assert deferred_tool_use is None when absent

…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.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants