feat(plugin): PreToolUse hook to reject direct git worktree commands#54
feat(plugin): PreToolUse hook to reject direct git worktree commands#54
Conversation
Intercepts Bash tool calls containing `git ... worktree` and denies them with additionalContext directing Claude to use grove skills instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @emmax86's task in 2m 27s —— View job Code Review
Overall: Well-designed, carefully implemented hook. The structural tokenizer handles the hard cases cleanly, the fail-open behaviour is correct, and the test matrix is thorough. Several automated reviewer comments above are false positives — see notes below. No blocking issues found.
|
| Claim | Status |
|---|---|
--exec-path missing from VALUE_FLAGS |
False positive — present on line 36 |
!tokens[i].startsWith("-") prevents consuming --prefixed values |
False positive — that guard applies to FLAGS only; values are consumed unconditionally |
jq / shell portability issues |
N/A — this is a TypeScript file invoked via bun run, not a shell script (the .sh version was superseded) |
stderr pipe deadlock |
Already fixed — stderr: "inherit" on line 13 |
invokeScript treats crash (exit 1) as allow |
Already fixed — lines 19–21 throw on unexpected codes |
JSON.parse crash on malformed input |
Already fixed — try/catch + exit 0 on lines 136–140 |
Verdict
LGTM. No blocking issues. The implementation is solid and the known limitations are correctly scoped and documented. The one optional improvement is adding a locked-in test for the 1>&2-prefix bypass so the gap stays intentional rather than accidental.
There was a problem hiding this comment.
Pull request overview
Adds a Grove Claude Code PreToolUse hook intended to deny direct git … worktree Bash invocations and steer users toward Grove worktree skills, with integration tests validating deny/allow behavior and JSON output.
Changes:
- Add
plugins/grove/hooks/reject-git-worktree.shto detect and denygit … worktreecommands. - Register the hook in
plugins/grove/.claude-plugin/plugin.jsonunderhooks.PreToolUsefor the Bash tool. - Add
src/__tests__/integration/hooks.test.tsintegration tests that execute the hook script viaBun.spawn.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/__tests__/integration/hooks.test.ts |
Adds integration tests for allow/deny cases and deny JSON structure. |
plugins/grove/hooks/reject-git-worktree.sh |
Implements the deny logic and returns hook-protocol JSON on matches. |
plugins/grove/.claude-plugin/plugin.json |
Wires the hook into the plugin’s PreToolUse Bash matcher. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8ffb463db2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Replace greedy .* with flag-aware POSIX ERE pattern to prevent false positives when "worktree" appears in commit messages or file paths - Anchor regex to ^ so git must be the invoked command (fixes echo git worktree) - Use [[:space:]] for BSD grep portability instead of \s/\b - Guard jq failure with || COMMAND="" so set -e does not exit the hook - Invoke script via `bash` in plugin.json for cross-platform safety - Add tests: git commit -m "worktree...", echo git worktree - Remove TDD cycle comments from test file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace CLI-spawning approach with @anthropic-ai/claude-agent-sdk, avoiding the nested Claude Code session restriction - Register the hook shell script as a TypeScript HookCallback so the real script is exercised through the agent pipeline - Run each test against an isolated throwaway git repo (mkdtemp + afterEach cleanup) to prevent pollution of the grove repo itself - Assert on hook.denied() rather than Claude's response text - Add .grove/commands.json so `grove ws exec test:file` works - Skip tests automatically when CLAUDECODE env var is set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace direct bash-spawning with @anthropic-ai/claude-agent-sdk so tests exercise the hook through the real agent tool pipeline - Register the hook shell script as a TypeScript HookCallback; assert on hook.denied() rather than Claude's response text - Isolate each test in a throwaway git repo (mkdtemp/afterEach) to prevent pollution of the grove repo itself - Skip tests automatically when CLAUDECODE env var is set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e tokenizer - Replace bash regex with a TypeScript tokenizer that splits on shell operators (; & | newline) before checking each segment, catching chained commands like `echo ok; git worktree list` - Structural token walk (env vars → git → VALUE_FLAGS → subcommand) replaces single ERE regex, fixing false positives like `git -C worktree list` (worktree as -C argument) - Remove @anthropic-ai/claude-agent-sdk dependency; tests invoke the hook script directly via bun, no claude needed in CI - Expand test suite to table-driven deny/allow cases including shell separators, env var prefixes, flag value consumption, and fail-open edge cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a Claude Code PreToolUse hook to block direct git worktree ... Bash commands in grove workspaces, nudging users toward grove’s built-in worktree skills, and includes integration tests to validate the hook behavior.
Changes:
- Added a Bun hook script that detects
git worktreesubcommands and returns a deny decision with guidance text. - Wired the hook into the grove plugin’s
plugin.jsonfor BashPreToolUse. - Added table-driven integration tests that execute the hook script via
bun run. - Added a repo-level
.grove/commands.jsoncommand configuration.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
plugins/grove/hooks/reject-git-worktree.ts |
Implements structural detection for git worktree and emits deny JSON for PreToolUse. |
plugins/grove/.claude-plugin/plugin.json |
Registers the new PreToolUse Bash hook command with a timeout. |
src/__tests__/integration/hooks.test.ts |
Adds integration tests that run the hook script and assert allow/deny behavior and output shape. |
.grove/commands.json |
Provides standard repo command mappings (setup/format/test/check). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap JSON.parse in try/catch to fail open on malformed stdin - Extract command via typed guard (extractCommand) instead of unsafe optional chaining with string cast - Test helper: stderr: "inherit" to surface hook errors without pipe deadlock; throw on unexpected exit codes so crashes fail tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a Claude Code PreToolUse hook for the grove plugin to block direct git worktree usage in Bash tool calls, and introduces integration tests that invoke the hook script via the hook protocol.
Changes:
- Added
plugins/grove/hooks/reject-git-worktree.tsto detect and denygit worktreeinvocations. - Wired the hook into
plugins/grove/.claude-plugin/plugin.jsonunderhooks.PreToolUsefor Bash. - Added table-driven integration tests in
src/__tests__/integration/hooks.test.tsthat execute the hook viabun run.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
plugins/grove/hooks/reject-git-worktree.ts |
Implements structural detection of git worktree and emits deny JSON on match. |
plugins/grove/.claude-plugin/plugin.json |
Registers the new hook for Bash PreToolUse events. |
src/__tests__/integration/hooks.test.ts |
Adds integration coverage for deny/allow cases and output JSON shape. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
- Replace split(/\s+/) with a minimal shell tokenizer that strips single/double quotes and handles backslash escapes, so git "worktree" list and git 'worktree' list are correctly denied - Add deny cases for quoted subcommand and quoted env var assignment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a Claude Code (grove) PreToolUse hook to block direct git worktree usage in Bash tool calls, steering users toward grove’s /worktree commands/skills, and introduces integration tests to validate the hook behavior.
Changes:
- Added
reject-git-worktree.tsPreToolUse hook script that detectsgit worktreeacross simple shell-chained commands and denies execution with guidance. - Wired the hook into the grove plugin manifest (
plugins/grove/.claude-plugin/plugin.json) for the Bash matcher. - Added table-driven integration tests that invoke the hook via
bun runand assert allow/deny behavior and deny JSON shape.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/__tests__/integration/hooks.test.ts |
Adds integration tests exercising the hook script through Bun spawn and checking deny payload structure. |
plugins/grove/hooks/reject-git-worktree.ts |
Implements command splitting/tokenization and deny output for git worktree detection. |
plugins/grove/.claude-plugin/plugin.json |
Registers the new PreToolUse hook for Bash tool calls. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Add 8 new DENY cases (multiple VALUE_FLAGS stacked, multiple env vars, pipe/&&/newline segments) and 6 new ALLOW cases (worktree in filenames, grep patterns, config keys, and -c values). 39 tests total, all passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a Claude Code PreToolUse hook to the grove plugin to deny direct git worktree usage (nudging users toward grove skills), wires it into the plugin manifest, and adds integration tests that exercise the hook script via the hook protocol.
Changes:
- Added a Bun-based PreToolUse hook script that detects and denies
git worktreeinvocations. - Registered the hook in
plugins/grove/.claude-plugin/plugin.jsonfor theBashtool. - Added table-driven integration tests to validate deny/allow behavior and deny JSON output structure.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
plugins/grove/hooks/reject-git-worktree.ts |
Implements command tokenization + structural detection and emits a deny response for git worktree. |
plugins/grove/.claude-plugin/plugin.json |
Registers the new PreToolUse hook for Bash tool invocations. |
src/__tests__/integration/hooks.test.ts |
Adds integration coverage by invoking the hook script as a subprocess across allow/deny cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
- Treat (, ), {, } as segment boundaries so (git worktree list),
{ git worktree list; }, and $(git worktree list) are all detected
- Add --exec-path and --super-prefix to VALUE_FLAGS so tokens like
`git --exec-path worktree list` correctly consume worktree as the
flag value rather than misidentifying it as the subcommand
- Update known-limitation comment: $(…) is now caught; backtick
substitution and >&-prefixed redirections remain out of scope
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Adds a Claude Code `PreToolUse` hook to the grove plugin that intercepts Bash tool calls containing `git worktree` and denies them, steering Claude toward grove's `/worktree` skills and `create-grove-worktree` instead.
Tokenizer highlights
Test plan