[FEATURE] Run PostToolUse hooks when a mod's tool.call hook supplies the result
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code 2.1.299, mods / function hooks. A hooks module can answer tool.call with its own { result } (no next(e)), which replaces the tool's execution. For such a call Claude Code fires no PostToolUse hooks at all: settings hooks and plugin command hooks matching the tool never run. The same call routed through next(e) runs them normally.
So a mod cannot serve a tool result (e.g. a faster Grep from a code-search plugin's index) without silently breaking every other hook on that tool: audit logs, memory/telemetry collectors, linters, chat bridges, policy hooks. Hooks used as policy or telemetry should hold regardless of what produced the result, a core run or a hook. A mod also cannot work around it: there is no documented way to dispatch PostToolUse itself or to list which settings/plugin hooks match a tool ($.classic.* dispatch exists only in the test kit).
Minimal repro (isolated: claude -p --setting-sources project --settings settings.json --plugin-dir ./mod):
// mod hooks module
on('tool.call', { tool: 'Grep' }, () => ({
result: { mode: 'content', content: 'a.go:1:x', numFiles: 1, filenames: ['a.go'], numLines: 1 },
}));
// settings.json
{ "hooks": { "PostToolUse": [ { "matcher": "Grep",
"hooks": [ { "type": "command", "command": "node -e \"require('fs').appendFileSync('post.log','post\\n')\"" } ] } ] } }
- Observed: model calls Grep, gets the supplied result;
post.logis never written. - Expected:
post.loggets a line, as it does when the hook returnsnext(e)instead.
Proposed Solution
For a tool.call answered by a hook-supplied result, run the same post-call pipeline as for a core-run call: PostToolUse (or PostToolUseFailure) with the supplied result as tool_response, from settings, plugin and module hooks, including updatedToolOutput / decision / additionalContext handling. Ideally also keep PreToolUse and the permission check ahead of tool.call hooks, so a mod's result cannot bypass them either.
Alternative Solutions
If that is not wanted by default: expose a documented API for a mod to do it itself, e.g. $.hooks.run('PostToolUse', envelope, result) (and PreToolUse), or at least a way to list the hooks that match a tool, so a mod can decide to fall through to next(e) when other hooks would be skipped. Today the only safe option is to never supply a result.
Priority
Medium - Would be very helpful
Feature Category
Developer tools/SDK
Use Case Example
- A code-search plugin answers Grep from its index in ~100 ms instead of running ripgrep (300-700 ms).
- The user also has a PostToolUse hook on
*that bridges tool activity to another agent, and a memory hook that records every search. - With a supplied result both hooks miss every served Grep, so the plugin has to turn serving off.
- With PostToolUse run for supplied results, the plugin can serve and every other hook keeps its semantics.
Additional Context
Related, not the same: #96831 and #100869 (classic.* events not dispatched to hook modules), #101073 (ordering of multiple hooks' updatedInput / updatedToolOutput).