feat(core): gate tool use with hooks - #1155
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
# Conflicts: # src/core/task/__tests__/Task.persistence.spec.ts # webview-ui/src/components/settings/__tests__/HooksSettings.spec.tsx
edelauna
left a comment
There was a problem hiding this comment.
Nice! Cool feature. Had a couple comments.
| beforeMcpExecution: async () => | ||
| (await checkToolRepetition(cline, block, pushToolResult)) && | ||
| (await runPreToolHooks(cline, "use_mcp_tool", pushToolResult)), | ||
| }) |
There was a problem hiding this comment.
Is there a test covering the use_mcp_tool path through beforeMcpExecution? The static-tool path has spec coverage, but removing this branch wouldn't fail any test in the suite.
| return true | ||
| } | ||
| if (!cline.abort && result.status !== "cancelled") { | ||
| pushToolResult(formatResponse.toolError(result.reason)) |
There was a problem hiding this comment.
What happens to the tool result when result.status === "cancelled"? The caller skips pushToolResult here, but is there anything upstream (e.g. validateAndFixToolResultIds) that synthesises a fallback, and is that path tested?
| return Object.freeze(snapshot) | ||
| }) | ||
| .catch(() => Object.freeze([])) | ||
| this.globalStoragePath = provider.context.globalStorageUri.fsPath |
There was a problem hiding this comment.
If getState() rejects, the snapshot silently falls back to [], disabling all hooks with no log. Should there be a console.error here so operators know hooks are inactive?
| this.globalStoragePath = provider.context.globalStorageUri.fsPath | |
| .catch((err) => { | |
| console.error("[hooks] Failed to load hook definitions; hooks disabled:", err) | |
| return Object.freeze([]) | |
| }) |
| .min(1) | ||
| .max(256) | ||
| .refine((value) => !value.includes("\0"), "Tool name must not contain NUL characters") | ||
| export type HookToolName = z.infer<typeof hookToolNameSchema> |
There was a problem hiding this comment.
The .refine() predicate here duplicates the nulFreeString factory defined a few lines above (line 11 uses the same pattern). Could this use nulFreeString(256, "Tool name") directly?
| context: [], | ||
| status: "cancelled", | ||
| reason: "The task was cancelled.", | ||
| }) |
There was a problem hiding this comment.
Is there a test for the taskLifetimeAbortController.signal.aborted branch inside runPreToolUseHooks? The instance-replacement path is covered, but the abort-signal cancellation path doesn't appear to have its own case.
|
|
||
| ## Maintaining this file | ||
|
|
||
| Keep this file for knowledge useful to almost every future agent session in this project. |
There was a problem hiding this comment.
is this useful to include? This feels rather generic and pretty standard, and possibly communicated throughout the repo already.
Stack
This is 4 of 4 in the Zoo Code hooks MVP stack. Review and merge from the bottom upward. Do not merge this PR until #1156 is merged.
mainfm/zoo-hooks-contractsfm/zoo-hooks-settingssessionStartintegrationfm/zoo-hooks-session-startpreToolUse, E2E, and documentationSummary
Complete the hooks MVP by applying fail-closed
preToolUsegates at every supported static, custom, and native MCP execution boundary, then document and exercise the full workflow in a real VS Code host.Scope
preToolUsehooks after tool and argument validation but before approval, checkpointing, or execution.docs/hooks.md.Tool Gate
flowchart TD A[Model requests tool] --> B[Resolve tool and validate arguments] B -->|invalid or disallowed| C[Existing validation result] B -->|valid| D[Find exact preToolUse hooks] D --> E[Run hooks sequentially] E -->|all allow| F[Approval and checkpoint flow] F --> G[Execute static, custom, or MCP tool] E -->|block, error, timeout, cancel| H[Skip approval and execution] H --> I[Emit exactly one real tool result] G --> ITests
src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.tssrc/core/assistant-message/__tests__/presentAssistantMessage-images.spec.tssrc/core/task/__tests__/Task.persistence.spec.tssrc/core/tools/__tests__/useMcpToolTool.spec.tssrc/core/webview/__tests__/webviewMessageHandler.spec.tswebview-ui/src/components/settings/__tests__/HooksSettings.spec.tsxpackages/types/src/__tests__/hooks.test.tsapps/vscode-e2e/src/suite/hooks.test.tspnpm testpassed (437 files, 7,356 tests; 39 skipped).pnpm check-types,pnpm lint, andpnpm buildpassed.USE_MOCK=true TEST_FILE=hooks.test pnpm --filter @roo-code/vscode-e2e test:runpassed.Risks
Review Notes
fm/zoo-hooks-session-start, notmain.da0d545b6carries the intermediate-branch CI fixes forward without rewriting published history.e484eeae8carries the Windows runner portability fix from#1156into this top branch.72fb1097fcarries the explicit Windows system-binary path fix forward.dffdf7390carries deterministic Windows-path coverage forward.2e98360d6carries the final taskkill fallback cleanup forward.#1153->#1154->#1156->#1155.