From 6c2f2bd1de7cb3e3361d9e572b8ea6fbc3b71fed Mon Sep 17 00:00:00 2001 From: Jacky Raimond Date: Thu, 13 Aug 2026 10:55:28 +0200 Subject: [PATCH] Put git-guardrails on exec form, and say what monitors do instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-guardrails was merged the same day the exec-form rule was written, and kept the shell form it was authored with. It is now the only hook not following its own CONTRIBUTING, so this moves it over: python3 in `command`, the script in `args`. The rule was also easy to read as applying to monitors, which cannot follow it. A monitors.json entry is name, command, description and an optional `when`; there is no `args`, and `command` is always handed to a shell — the upstream reference documents exactly the quoted form the monitors here use. So the bullet now says which of the two applies where, rather than leaving every monitor looking like it is out of line. Co-Authored-By: Claude Opus 5 (1M context) --- CONTRIBUTING.md | 15 +++++++++++++-- hooks/git-guardrails/hooks/hooks.json | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac57fd9..2274b5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,8 +20,10 @@ context. Review PRs here with that in mind, and expect the same of your own. - Every hook ships a `README.md` covering: what it does, which event and matcher, what it blocks or changes, how to test it, and how to disable it. A monitor's README covers what it watches, what it prints, how to test it, and how to turn it off. -- Reference bundled scripts with **exec form**: put the interpreter in `command` and the script in - `args`, using `${CLAUDE_PLUGIN_ROOT}`. Never a relative path, never a path on your own machine. +- Reference bundled scripts with `${CLAUDE_PLUGIN_ROOT}`. Never a relative path, never a path on + your own machine. + + In a **hook**, use **exec form**: the interpreter in `command`, the script in `args`. ```json { "type": "command", "command": "python3", @@ -31,6 +33,15 @@ context. Review PRs here with that in mind, and expect the same of your own. Exec form spawns the script directly instead of handing a string to `sh -c`, so quoting, spaces and `$` in a path stop being your problem. Shell form still works and is the right choice when you genuinely need a pipe or `&&`, but then every placeholder must be double-quoted. + + A **monitor** has no exec form to choose: a `monitors.json` entry is `name`, `command`, + `description` and an optional `when`, and `command` is always a shell string. So double-quote + every placeholder, exactly as the entries here do. + + ```json + { "name": "my-monitor", "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/my-monitor.sh", + "description": "What is being watched" } + ``` - Configuration that decides **what a hook is allowed to do** belongs in `userConfig` in `plugin.json`, not in a file inside the project. Claude Code prompts for those values when the plugin is enabled, stores them in user settings, and passes them to the hook as diff --git a/hooks/git-guardrails/hooks/hooks.json b/hooks/git-guardrails/hooks/hooks.json index c0f882f..4335c38 100644 --- a/hooks/git-guardrails/hooks/hooks.json +++ b/hooks/git-guardrails/hooks/hooks.json @@ -6,7 +6,8 @@ "hooks": [ { "type": "command", - "command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/git-guardrails.py", + "command": "python3", + "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/git-guardrails.py"], "if": "Bash(git *)", "timeout": 10 }