Examples
A set of working hooks you can copy into your project. Each shows a different use case, so you can modify them as you see fit. All are bash scripts here, but a hook can be any executable that parses JSON on stdin.
| Example | Event | Kind | What you get |
|---|---|---|---|
| Block dangerous bash | PreToolUse | Blocking | Model can't run rm -rf /, curl | sh |
| Warn on sensitive reads | PreToolUse | Context injection | Model is told to redact secrets before quoting |
| Audit tool calls | PreToolUse or PostToolUse | Audit only | Matching tool calls appended to a local log file |
| Quality gate | Stop | Blocking (retry) | Assistant can't finish with DO NOT SHIP markers left in the code |
Every example has two files:
- A
settings.jsonthat wires the hook. - A shell script at
.commandcode/hooks/<name>.sh.
Ensure each script is executable with chmod +x before Command Code fires the hook.
Each example ends with a Test block containing a prompt to paste into Command Code and the result to expect so you can confirm the hook fires.
Match the model's shell command against a short list of dangerous patterns. On a hit, deny the tool and tell the model why, so it doesn't retry the same pattern.
.commandcode/settings.json
.commandcode/hooks/deny-dangerous.sh
Test: Block dangerous bash commands
Prompt in Command Code:
Expected result:
- ❌ The command is blocked before it runs (it matches the
rm -rf /pattern, even though the target is harmless). - The agent sees "Command matched a dangerous pattern. Policy forbids…" and moves on without retrying.
How it works
- Reads
tool_input.commandfrom stdin withjq. - Matches against four patterns:
rm -rf /,curl | sh, a:(){}fork bomb,sudo rm. - On a hit, emits
permissionDecision: "deny". The tool is skipped and the model receivespermissionDecisionReasonas the tool result. - On a miss, exits
0with no stdout. The tool runs normally.
Allow every read, but quietly inject a note when the path looks sensitive. The model sees the note as extra context and adjusts its response.
.commandcode/settings.json
.commandcode/hooks/warn-sensitive-reads.sh
Test: Warn on sensitive reads
Prompt in Command Code:
Expected result:
- ✅ The read is allowed. The hook never blocks it.
- The agent receives the injected note "SENSITIVE READ: …/.env. Redact any keys or tokens before quoting from this file." and refuses to paste the full contents.
How it works
- Always returns
permissionDecision: "allow". The Read is never blocked. - Sends
additionalContextto the model, appended to the tool result before the next turn. - Upgrades the note to an explicit redaction warning when the path matches
.ssh/,.env,.pem, orid_rsa.
Log every matching tool call to an append-only file. The hook writes nothing to stdout, so it's observe-only: the tool runs unchanged. The pattern (read tool_input with jq, append a tab-separated line) is the same for any tool. Swap the event, matcher, and extracted field to fit what you want to observe.
Use PostToolUse to log what completed. Use PreToolUse to log what was attempted; this also catches commands that a later hook denies, as long as the audit hook runs first.
Writes and edits (PostToolUse)
Fires after the file mutation completes. Uses COMMANDCODE_PROJECT_DIR and COMMANDCODE_SESSION_ID directly, so there's no need to parse cwd or session_id from stdin.
.commandcode/settings.json
.commandcode/hooks/audit-writes.sh
Test: Audit writes
Prompt in Command Code:
Expected result:
- ✅ The file is created.
- The hook appends one line to
.commandcode/write-audit.log. - Verify with
cat .commandcode/write-audit.log. You should see a tab-separated line (timestamp, session ID, file path).
Shell commands (PreToolUse)
Fires before execution, so this logs every shell command the agent issues.
.commandcode/settings.json
.commandcode/hooks/log-shell.sh
Test: Audit shell commands
Prompt in Command Code:
Expected result:
- ✅ The command runs as normal.
- The hook appends one line to
/tmp/cmd-shell.log. - Verify with
cat /tmp/cmd-shell.log. Each entry in/tmp/cmd-shell.loghas the shape (fields are tab-separated):
Block the agent from finishing the turn while a DO NOT SHIP marker is still in the code. The Stop event fires at end of turn; this hook greps for the marker and exits 2 when it finds one, sending the assistant back for another pass with the offending lines as feedback. The stop_hook_active check is the canonical loop-prevention pattern. Without it, the hook would fire forever.
.commandcode/settings.json
.commandcode/hooks/no-ship-gate.sh
Test: Quality gate
Prompt in Command Code:
Expected result: the turn is blocked, the agent retries on its own, and it finishes once the marker is gone. Step by step:
- ❌ First turn: the assistant finishes → the hook greps → finds the
DO NOT SHIPmarker → exits2with the matching lines on stderr. A gray Stop frame appears in the feed:
- The engine feeds that stderr text back to the model as a user-role message and re-runs the turn.
- ✅ Second turn: the assistant removes the marker and adds error handling. The hook fires again with
stop_hook_active: true→ exits0. The turn ends.
If the hook keeps blocking (e.g. it's broken), the engine caps retries at 3 and ends the turn with Stop hook retry cap reached (3). Ending turn. Same outcome ×4. Fix or disable: ./.commandcode/hooks/no-ship-gate.sh.
- Hooks overview: back to the big picture
- Configuration: scopes, precedence, and ordering
- Best Practices: write safe hooks and debug common issues
- Hooks Reference: full schema for input, output, and exit codes