-
Notifications
You must be signed in to change notification settings - Fork 3
Function hooks can answer a tool call, so Read/Write can be served by supertool instead of refused #2293
Copy link
Copy link
Open
Labels
cohort-29Open at the v0.60.0 tag, 2026-09-11. Frozen: nothing joins a cohort.Open at the v0.60.0 tag, 2026-09-11. Frozen: nothing joins a cohort.cohort-30Open at the v0.61.0 tag, 2026-09-12. Frozen: nothing joins a cohort.Open at the v0.61.0 tag, 2026-09-12. Frozen: nothing joins a cohort.cohort-31Open at the v0.63.0 tag, 2026-09-19. Frozen: nothing joins a cohort.Open at the v0.63.0 tag, 2026-09-19. Frozen: nothing joins a cohort.cohort-32Open at the v0.64.0 tag, 2026-09-23. Frozen: nothing joins a cohort.Open at the v0.64.0 tag, 2026-09-23. Frozen: nothing joins a cohort.enhancementNew feature or requestNew feature or requestlane-containmentpayload gate and trust model — reaches into other lanes, wants their reviewpayload gate and trust model — reaches into other lanes, wants their reviewpriority-mediumRecurring cost; compounds if unfixedRecurring cost; compounds if unfixed
Milestone
Description
Activity
Metadata
Metadata
Assignees
Labels
cohort-29Open at the v0.60.0 tag, 2026-09-11. Frozen: nothing joins a cohort.Open at the v0.60.0 tag, 2026-09-11. Frozen: nothing joins a cohort.cohort-30Open at the v0.61.0 tag, 2026-09-12. Frozen: nothing joins a cohort.Open at the v0.61.0 tag, 2026-09-12. Frozen: nothing joins a cohort.cohort-31Open at the v0.63.0 tag, 2026-09-19. Frozen: nothing joins a cohort.Open at the v0.63.0 tag, 2026-09-19. Frozen: nothing joins a cohort.cohort-32Open at the v0.64.0 tag, 2026-09-23. Frozen: nothing joins a cohort.Open at the v0.64.0 tag, 2026-09-23. Frozen: nothing joins a cohort.enhancementNew feature or requestNew feature or requestlane-containmentpayload gate and trust model — reaches into other lanes, wants their reviewpayload gate and trust model — reaches into other lanes, wants their reviewpriority-mediumRecurring cost; compounds if unfixedRecurring cost; compounds if unfixed
Claude Code 2.1.260 ships function hooks behind
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1(flagtengu_plugin_hooks_modules, GrowthBook-gated, env var overrides). A plugin declares one hooks module inhooks/hooks.json; the module hooks events and calls affordances on a$object.The part that matters here is not that hooks can now be written as functions. It is that a hooks module can resolve a tool call with a result, not only allow, ask or deny. From the 2.1.260 darwin-arm64 binary:
denyis one branch.resultis the other, and on that branch the tool never runs.This closes the gap the shell-hook design could not. A
PreToolUsecommand hook getspermissionDecision,permissionDecisionReason,updatedInput,additionalContext,systemMessage— it can rewrite arguments inside the same tool's schema, and it can refuse, but it cannot answer. So the only way to route an agent fromReadtoread:PATHwas to deny and hope the retry picks supertool: a wasted turn, a refusal in the transcript, and an agent that may route around the guard instead (#2007 measured a subagent objecting to the redirection itself).A hooks module removes the turn. Hook
tool.call, and forRead,Grep,Globand the write-class tools, run the equivalent op and hand back its output as the tool result. The model asked forRead, got supertool's answer with its↳ to modify:footer, and never learned there was a substitution. Nothing is denied, so nothing is retried.That is the premise of this repo applied one layer lower than it has ever reached: a round-trip saved is a whole model turn, and this saves the refusal turn and the re-issue turn on every mis-routed call.
Open questions this issue does not settle:
Readtoread:PATHis the obvious one. The write class is riskier: a served result that claims a write landed when it did not is exactly the defect class inCLAUDE.md("an unverified success and a verified one must never render alike"), and a hooks module is a new place for it to hide.presets/_untrusted.pydraws one such boundary already; this needs its own.PreToolUseguard has to stay correct on its own, and the two must not both fire.$surface. Observed in the binary:$.tool.call,$.tool.register,$.tool.list, plus a generatedclaude-code.d.tsto type the module against. Not read yet.Related: #1253 (PostToolUse can rewrite tool results — the same idea from the other side, and weaker: it needs the tool to have already run).
Verified 2026-09-04 against
@anthropic-ai/claude-code-darwin-arm64@2.1.260and@2.1.261, unpacked from npm.2.1.258does not carry the flag: 0 hits.2.1.260and2.1.261: 7 each. The docs at code.claude.com/docs/en/hooks still list five hook types (command,http,mcp_tool,prompt,agent) and none of them is this, so the binary is the only source right now.