Skip to content

feat: add deferred tool use example with human-in-the-loop flow#1062

Open
highgroundbkk wants to merge 4 commits into
anthropics:mainfrom
highgroundbkk:main
Open

feat: add deferred tool use example with human-in-the-loop flow#1062
highgroundbkk wants to merge 4 commits into
anthropics:mainfrom
highgroundbkk:main

Conversation

@highgroundbkk

@highgroundbkk highgroundbkk commented Jun 20, 2026

Copy link
Copy Markdown

Summary

  • Adds examples/deferred_tool_use.py demonstrating the human-in-the-loop pattern using permissionDecision: \"defer\" in a PreToolUse hook
  • Shows both allow and deny flows after a session pauses
  • Allow: resumes the exact session via resume=session_id with bypassPermissions
  • Deny: opens a fresh plan-mode session and informs Claude the command was blocked
  • Adds mypy PostToolUse hook to .claude/settings.json so type errors surface automatically after every edit

How it works

```
Turn 1: Claude attempts Bash

PreToolUse hook → permissionDecision: "defer"

Session PAUSES ⏸
ResultMessage.deferred_tool_use = {tool, input}

Human inspects and decides:
y → resume=session_id + bypassPermissions → Claude runs command
n → fresh plan-mode session → Claude suggests alternative
```

Changes

  • examples/deferred_tool_use.py — new example demonstrating deferred tool use / human-in-the-loop
  • .claude/settings.json — adds uv run --extra dev python -m mypy src/ --no-error-summary as a PostToolUse hook on Edit|Write|MultiEdit

Test plan

  • echo "y" | uv run python3 examples/deferred_tool_use.py — allow flow runs the command
  • echo "n" | uv run python3 examples/deferred_tool_use.py — deny flow suggests alternative without executing
  • Edit any file under src/ and confirm mypy runs automatically via the new hook

🤖 Generated with Claude Code

- Fix module docstring: allow flow uses resume=session_id, not continue_conversation+extra_args
- Fix deny flow docstring: opens fresh plan-mode session, not a follow-up
- Add session_id None guard before resume= to prevent silent wrong-session bug
- Handle ResultMessage.is_error in display() so failed sessions are visible
- Fix deny prompt: fresh session framing instead of misleading 'you previously...'
- Use json.dumps(deferred.input) instead of .get('command', fallback) to avoid silent data loss
- Fix display() type annotation: object -> Message
- Add Usage: section to module docstring
- Remove emoji from print() output per CLAUDE.md
Copilot AI review requested due to automatic review settings June 28, 2026 16:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new example showcasing “deferred tool use” (human-in-the-loop approval) and updates Claude Code project settings to automatically run mypy after edit/write operations.

Changes:

  • Add examples/deferred_tool_use.py to demonstrate permissionDecision: "defer" in a PreToolUse hook, including allow/resume and deny/plan flows.
  • Update .claude/settings.json to run mypy (uv run ... python -m mypy src/ --no-error-summary) as a PostToolUse hook after Edit|Write|MultiEdit.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
examples/deferred_tool_use.py New end-to-end demo for deferred tool execution with explicit human approval/denial flows.
.claude/settings.json Adds automatic mypy execution as a PostToolUse hook after file edits/writes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +117 to +121
resume_options = ClaudeAgentOptions(
allowed_tools=["Bash"],
permission_mode="bypassPermissions",
resume=session_id,
)
Comment on lines +82 to +88
options = ClaudeAgentOptions(
allowed_tools=["Bash"],
permission_mode="default",
hooks={
"PreToolUse": [HookMatcher(matcher="Bash", hooks=[defer_for_approval])],
},
)
print(f" Tool : {deferred.name}")
print(f" Input : {deferred.input}")
else:
cost = f"${msg.total_cost_usd:.4f}" if msg.total_cost_usd else "n/a"
Comment on lines +106 to +109
if session_id is None:
print("\nError: no session ID captured; cannot resume.")
return

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