The day AI decided to "clean up"
The project's gone.
A coworker's AI CLI tool deleted the entire project folder and almost made it unrecoverable. Now everyone is running AI with full permissions - full auto without guardrails is a full auto disaster.
That day, AI deleted the folder.#
True story, two coworkers, two different CLI tools#
Some of our team use OpenAI Codex CLI, some use Google Gemini CLI. both tools are very powerful, but neither of them have any hooks set up.
One day, one of the colleagues asked the AI to clean up the temporary files, but the AI's judgment was wrong and deleted the whole project folder.
# AI thinks it's part of the "cleanup"
# There's no mechanism to intercept this command
Luckily, I had git history and Time Machine backups, so it took me a while to recover. Another colleague encountered a similar situation - the AI misjudged the path when organizing files and deleted directories that should not be deleted.
The common denominator in both cases: there was no problem with the CLI tool itself. The problem was the lack of guardrails.
AI is a probabilistic system.Even if prompt writes "don't delete important files," the model may still make a different judgment in a given inference. hooks are deterministic - therm-rf / It will always be blocked, no matter what the AI thinks.
Nobody's going one by one, Allow.#
Theoretically, AI CLI tools have a permission checking mechanism. You will be asked to allow or deny each time you execute a command.
In practice, no one uses this. Hundreds of times a day, the development efficiency goes to zero. That's why everyone has maximum permissions:
claude --dangerously-skip-permissions
# OpenAI Codex CLI - YOLO mode, automatically approves everything!
codex --approval-mode full-auto
# Google Gemini CLI - Same YOLO mode!
gemini --sandbox=false
This is not the behavior of a few.This is the norm.Full permissions are the only way to make an AI agent truly effective.
But full permissions mean that the AI can do anything: delete files, force push, write to system directories, push commits with keys, etc. Being fully automated with no guardrails is like handing your development environment over to a system that's "smart most of the time, but occasionally makes mistakes".
The question isn't "Should I turn on full permissions" - it's "Who's going to block the occasional one when I do?"
Scattered Shell Scripts get out of hand#
After that incident, we started to add hooks to Claude Code, which started naturally: write a few shell scripts to block dangerous commands.
Three months later, the situation was like this:
Scripts are scattered all over the place.#
Dozen.sh files, some in the hooks directory, some in the scripts directory, and some in the home directory. Each of these repeatedly implements JSON parsing and error handling.
One crash, total shutdown.#
Claude Code's hooks mechanism is synchronized:One hook crashes and the entire pipeline is interrupted.
No on/off switch#
Want to temporarily disable a hook?settings.json Change it manually. After testing, I often forget to change it back.
A Dispatcher that manages all the Hooks.#
We integrate all the hooks into a modular framework: a dispatcher receives all the events and assigns them to each handler according to the profile.
Two-Phase Dispatch is the key design.Security class handler (e.g., blocking)rm-rfThe handler is labeled critical and is always the first to run, regardless of the time budget. The other handlers share a 5-second budget - timeouts are skipped without blocking the AI's workflow.
Block the AI's dangerous maneuvers.#
These handlers deal with the most critical security issues. They are marked as critical and are unconditionally prioritized.
stand in the way ofrm-rf /,sudo,git push --force,chmod 777 and other dangerous commands. Parsing commands with a quote-aware shell parser will not be bypassed by quotes or sub-shells.
existgit push Pre-scan diff to intercept AWS Key, GitHub Token, PEM Private Key, etc. Triggered only on push, does not interfere with local development. Support# nosec Line-by-line exemptions.
Blocking prompt injection attacks. Detect injections such as "ignore previous instructions",dangerouslyDisableSandbox and other privileged operations,curl Stolen data, such as outgoing data. Three types of threats, scanned independently.
"If we had bash_safety, our coworker's folder would not have been deleted. This was the first handler we wrote, and it was the starting point of the whole project."
Let the AI check its own work#
AI often commits code directly after writing it and skips verification. These handlers make sure that the checks are run before the commit.
stopgit commitcap (a poem)gh pr createThe AI is required to run the validation (lint, typecheck) first. The AI is required to run the validation (lint, typecheck) first, and write a marker file after passing it before releasing the commit.Disposable tokens: use it up and it's gone.
Sub-agent automatically runs lint and typecheck when it completes a mission, if it doesn't work, call the agent back to fix it. Maximum of 5 loops, 30 minute limit - release the agent if it exceeds this limit to avoid getting stuck.
The AI automatically runs ruff (Python) or biome (JS/TS) formatting after editing the file. Never block - even formatter crashes are silently released. Formatting consistency is good, but not worth interrupting the workflow.
Check for uncommitted code changes at the end of a Claude Code session. By default, it only sends alerts (no blocking), and needs to be manually opt-in to actually block. avoid AI leaving after doing something, and leaving the changes in the working tree.
Keep the AI from losing its memory.#
The context window of AI Agent is limited, and the sub-agent is blank when it is generated. These handlers deal with the "memory" problem.
Automatically injects project files when the Sub-agent is started. Read.context/ JSONL profile under the directory, each agent type can have different injection contents. The upper limit is 8000 characters to avoid bursting the context.
Memory relay between sessions. The handover file written before the end of the previous session is automatically injected when the new session is started. 5-minute TTL to prevent interference with expired data.
When the AI exits Plan Mode, it reminds you to save the plan to memory before starting the actual operation. Plan Mode consumes a lot of context, so it's a waste if you don't save it.One time reminder, no repeats.
See what the AI is doing.#
The AI Agent runs several sub-agents in the background at the same time, and if you don't know what they're doing, there's no way to track them down if something goes wrong.
Force each sub-agent to have a descriptive name. If no name is given, it will be blocked. Also suggest the appropriate agent type based on the content of the prompt - worker for writing code, explorer for looking up data, and writer for writing documents.
Write one line of JSONL to spool file for each hook event. Delay <1ms, does not affect any operation. The background program writes to database every 2 seconds for dashboard query.Zero network latency across the entire link.
When AI performs service restart or docker operation, it notifies the monitoring system to enter the maintenance window. It will be canceled automatically after the operation is finished. Lets the monitoring system know that "this downtime is expected", so that no false alarms will be generated.
The AI notifies you with TTS voice when it's done. Don't stare at the screen - go make coffee and it will call you when it's done. You'll also be prompted with a voice when you need to answer a question. A seven-layer guard mechanism filters the intermediate state of the sub-agent and only sounds when it's actually done.
Three principles that permeate all Handlers.#
Block-at-Submit, not Block-at-Write.#
AI doesn't block when it writes a file. It blocks when it wants to commit or push.
Reason:The workflow of AI is coherent. If you block at the time of Edit, the AI will lose the context and will not know where it has written. It is more reasonable to wait until it wants to submit the result before reviewing it.
Fail-Open, always#
Each handler is wrapped in atry/except Handler crash? Failed to read Config? Release it.
Reason:A guardrail should not be more dangerous than the thing it protects. A crashed handler shouldn't shut down an entire AI workflow.
Every rule has an escape hatch.#
secret_scan has# nosecThe following are some examples of how to do this: verify_commit has a marker mechanism, bash_safety has an environment variable switch, and verify_commit has a marker mechanism.
Reason:Rules without an escape hatch will eventually be shut down in their entirety. Giving the user a way to bypass it will keep the rules alive longer.
What's more important than what you do is what you don't do.#
- Do not intercept Edit / Write The AI does not intervene when writing a file. Interrupting the AI's write process will result in an incomplete file state. All reviews are deferred until commit or push.
- Run the test without being in the gate. verify_completion only runs lint and typecheck, deliberately excluding tests. Tests are too slow, have side effects, and can change database state. Putting it in an automated gateway is more risky than rewarding.
- Doesn't fix itself, just blocks and explains. bash_safety doesn't help the AI rewrite "safer commands". It just says "this command is blocked because X". Let the AI decide what to do next. Automatic rewriting tends to introduce weirder bugs.
- No context budget scores We wrote a context_supervisor handler (800 lines) that attempts to calculate context usage and suggests compression times. The concept was good, but the scoring model was inaccurate - the "importance" of context could not be determined by heuristic rules. Disabled.
- No Fail-Closed All handler crashes are released. Some people may ask "Isn't it dangerous?" -- even more dangerous is when the fences themselves jam the whole system. --What's more dangerous is that the fences themselves jam the whole system. The AI doesn't move, and the user doesn't know why.
hook-observatory#
MIT license, zero external dependencies, ready to use.
Three installation methods: Homebrew, Git Clone, or Desktop Installer.
Recommended for macOS. Two lines, no clone.
brew installhook-observatory
# Installed in Claude Code
hook-observatory
Universal for all platforms. Runs direct install scripts.
cdhook-observatory
python3 install.py
GUI guided installation, check handler, set tool path.
Required for first time macOS startup:xattr -cr installer.app
Copy to your AI Agent#
If you're also using the AI coding CLI, post this to it so it can help you assess which guards are needed.
Extended Reading#
Resources that were actually referenced in the process and influenced the design decision.
| Resources | Why is it important? |
|---|---|
| operonlab/hook-observatory | The open source projects discussed in this article |
| Blake Crosley: Why Each of My 95 Hooks Exists | The incident-driven approach to hook construction. Every hook has an incident behind it. |
| GitButler: Automate Your AI Workflows with Claude Code Hooks | One of the earliest hooks practices that inspired our initial design. |
| aiorg.dev: Claude Code Hooks Complete Guide | The "certainty vs. chance" framework accurately expresses the value proposition of hooks. |
| Jackle: Claude Code Voice Mode | A reference for implementing voice notification with hooks, which inspired the design of the voice_notify handler. |