Skip to content

feat(hooks): include tool_use_id in PreToolUse and PostToolUse hook input - #6217

Open
xxwtiancai wants to merge 1 commit into
OpenHands:mainfrom
xxwtiancai:feat/hook-tool-use-id-6214
Open

xxwtiancai wants to merge 1 commit into
OpenHands:mainfrom
xxwtiancai:feat/hook-tool-use-id-6214

Conversation

@xxwtiancai

@xxwtiancai xxwtiancai commented Oct 25, 2026 •

Copy link
Copy Markdown

HUMAN:

Summary

Add tool_use_id to HookEvent for Pre/PostToolUse hooks, enabling consumers to pair tool invocations with results during parallel execution and retries while aligning with Claude Code hook compatibility.

Problem

PreToolUse and PostToolUse hooks lacked a unique identifier for tool executions. During concurrent calls or retry attempts, audit, logging, and policy hooks could not correlate actions with their matching observations. Additionally, this missing field diverged from Claude Code's hook format, which exposes tool_use_id.

Changes

  • Event Payload: Added optional tool_use_id to HookEvent (Pre pulls from ActionEvent.tool_call_id, Post pulls the matching ID from ObservationEvent).
  • Transport & Compatibility: Propagates via standard stdin JSON/serialization paths as an optional field without breaking existing hooks.
  • Parity & Docs: Mirrored updates in TypeScript interfaces and updated README specifications.

Verification

  • Functional Tests: Verified matching IDs across Pre/Post hooks in real execution paths, confirmed non-tool events default to null, and ensured backward compatibility with older hook invocations.
  • Test Suites: tests/sdk/hooks passed (153 passed); TS suite passed (394 passed).
  • Quality Gates: Pre-commit checks passed cleanly.

AGENT:

Why

PreToolUse and PostToolUse hooks receive the tool name, its input and (for PostToolUse) its response, but nothing that identifies the tool call itself. When the agent runs the same tool twice with the same input — parallel tool calls or a retried command — a hook that sees both events cannot tell which result belongs to which call, which breaks audit, logging, and policy hooks that want to record a call and its result as one entry. The hooks contract documents Claude Code compatibility, and Claude Code's hook input includes tool_use_id for exactly this.

Summary

  • HookEvent gains an optional tool_use_id field (openhands/sdk/hooks/types.py): the ActionEvent.tool_call_id for PreToolUse and the paired ObservationEvent.tool_call_id for PostToolUse, so both hooks for one call see the same value; UserPromptSubmit/SessionStart/SessionEnd/Stop leave it null.
  • HookManager.run_pre_tool_use/run_post_tool_use accept an optional keyword-only tool_use_id; HookEventProcessor passes the event's tool_call_id from both _handle_pre_tool_use and _handle_post_tool_use. The field reaches command hooks through the existing stdin JSON and prompt/agent hooks through the existing serialized event — no separate plumbing.
  • Backward compatible: the field and the new arguments are optional, no existing parameter changed, and the serialized stdin JSON only gains a key.
  • clients/typescript/src/hooks/types.ts mirrors the same optional tool_use_id on the HookEvent interface.
  • examples/01_standalone_sdk/33_hooks/README.md documents the field in a new "Hook Input" section.

Issue Number

Closes #6214

How to Test

uv run pytest tests/sdk/hooks -q
cd clients/typescript && npm run build && npx vitest run

New tests in tests/sdk/hooks/test_tool_use_id.py exercise the real execution path: a command hook writes its stdin JSON to a file for PreToolUse and PostToolUse, and the test asserts both carry the same tool_use_id (call-abc-123) for one paired action/observation; a UserPromptSubmit hook asserts tool_use_id arrives as null; and a direct run_pre_tool_use call without the new argument asserts existing callers keep working. Full tests/sdk/hooks suite passes (153 tests) and the TypeScript client builds with all 394 tests passing.

Video/Screenshots

Not a visual change.

Design Doc

Single additive field following the existing HookEvent shape; no design doc added.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • The issue was unassigned with no competing PR at the time work started.
  • tool_use_id was chosen over tool_call_id to match the Claude Code hook contract naming the module already claims to follow.

…nput

Tool hooks received the tool name, input and response but nothing
identifying the tool call itself, so a hook watching two identical
calls (parallel or retried) could not pair a call with its result.
The Claude Code hook contract this module follows carries
tool_use_id for exactly that.

Add an optional tool_use_id to HookEvent, populated from the
ActionEvent's tool_call_id for PreToolUse and the paired
ObservationEvent's tool_call_id for PostToolUse, so both hooks for
one call see the same value; non-tool hook events leave it null.
The field reaches command hooks through the existing stdin JSON and
prompt/agent hooks through the existing serialized event, and the
run_pre_tool_use/run_post_tool_use keyword argument is optional, so
existing callers and scripts keep working. The TypeScript client
HookEvent mirror and the hooks example README document the field.

Closes OpenHands#6214

Co-authored-by: openhands <openhands@all-hands.dev>
@xxwtiancai
xxwtiancai marked this pull request as ready for review October 25, 2026 09:03
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.

[Feature]: Include the tool call id in PreToolUse and PostToolUse hook input

1 participant