Which of the default hooks can actually block a command (Chapter 3)

The default install writes eight hooks. Five run on PreToolUse, the moment before a tool call; two on PostToolUse, after it; one on Stop. A hook that exits 2 on PreToolUse stops the call and shows its message to the model.

Three of the eight carry a refusal. The other five cannot stop anything — they check syntax after an edit, watch the context window, strip comments so an allowlist matches, auto-approve read-only cd+git, and shout when a session dies on an API error. That ratio, three out of eight, is this whole book in one install: the set you get by default is mostly witnesses, and that is a reasonable design, but only if you know which is which.

These are the refusals, read out of the shipped scripts rather than from documentation.

Destructive commands (destructive-guard, PreToolUse on Bash). Nineteen distinct refusals, among them:

1 BLOCKED: 'rm -rf *' removes everything in the current directory.
2 BLOCKED: --no-preserve-root detected.
3 BLOCKED: the deletion target is set in a variable in this same command.
4 BLOCKED: a destructive command is hidden inside a wrapper.
5 BLOCKED: git reset --hard discards all uncommitted changes.

Note the third and fourth: the guard is not only matching a literal rm -rf. It refuses when the target is computed in the same command, and when the destructive call is wrapped, because those are the shapes that walk past a naive pattern.

Pushes (branch-guard, PreToolUse on Bash). Two refusals:

1 BLOCKED: Force push detected.
2 BLOCKED: Attempted push to protected branch.

Read the force-push rule carefully, because it is stricter than most people expect: it matches -f, --force and --force-with-lease. The safer form of a force push is refused too. The script’s own comment says why — git push --force-with-lease (still destructive). The way through is not a different flag but an explicit decision: set CC_ALLOW_FORCE_PUSH=1. Whether you agree with that call or not, it is the one your machine is running, and it is the kind of thing worth knowing before the moment you need it.

Credentials (secret-guard, PreToolUse on Bash). Four refusals:

1 BLOCKED: Attempted to stage .env file.
2 BLOCKED: Attempted to stage credential/key file.
3 BLOCKED: Attempted to stage a file matching CC_SECRET_PATTERNS.
4 BLOCKED: 'git add .' with .env file present.

The last is the useful one. Nobody types git add .env. They type git add . in a directory that happens to contain one.

If you went looking in the repository and found different wording. There is a second, smaller set in hooks/hooks.json — six hooks, all PreToolUse, with messages like BLOCKED: recursive rm on non-safe target. That file is a starter manifest for people assembling their own configuration. The npx install does not use it; it writes the eight above, whose bodies live in scripts.json. If the message you see does not match the message you read, that is which file you are looking at — not a version skew.

What this list does not cover

A hook sees one tool call at a time. It does not see intent, and it does not see the second half of a plan. Anything that reaches the filesystem without going through a watched tool, such as a script that was written earlier and is now merely being executed, is outside the window. Read the list above as a floor, not a ceiling.

A related note from writing this book: while drafting Chapter 4, the credential guard on this author’s own machine blocked the command that was writing this manuscript, because the manuscript quotes a dangerous command inside a code block. That is a false positive, and it is the honest shape of this trade. A guard tight enough to catch the real thing will sometimes catch you writing about the real thing.