That day, the AI decided to "clean up"
The project is over.
A colleague’s AI CLI tool deleted an entire project folder, and we almost couldn’t recover it. Now everyone is running AI with full permissions—fully automated systems without safeguards are nothing but a disaster.
That day, the AI deleted the folder
A true story: two coworkers, two different CLI tools
Two colleagues each had things deleted by AI: one used OpenAI Codex CLI, and the other used Google Gemini CLI. Both tools are very powerful, but they only realized afterward that neither had set up any hooks.
The first colleague asked the AI to clean up the temporary files, but the AI misidentified them and deleted the entire project folder.
# AI considers this part of the "cleanup"
# There is no mechanism to intercept this command
It took quite a while to recover the data using Git history and Time Machine backups. A second colleague also ran into trouble: while an AI was organizing files, it misidentified a path and deleted a directory it shouldn't have.
What these two incidents have in common: There was nothing wrong with the CLI tool itself; the problem was the lack of safeguards.
AI is a probabilistic system. Even if the prompt says, “Do not delete important files,” the model may still make a different decision during a particular inference. Hooks are deterministic—rm -rf / will always be blocked, no matter what the AI thinks.
No one is allowing them one by one
In reality, no one clicks allow over and over again—AI CLI tools theoretically all have a permission confirmation mechanism, asking for allow or deny before executing each command, but having to click it hundreds of times a day completely kills development efficiency, so everyone just enables the highest privileges:
claude --dangerously-skip-permissions
# OpenAI Codex CLI — YOLO Mode: Automatically Approve Everything
codex --approval-mode full-auto
# Google Gemini CLI — The Same YOLO Model
Gemini --sandbox=false
This isn't something only a few people do—this is the norm: Opening full permissions is the only way to let an AI agent truly unleash its efficiency, but the price is that the AI can execute any command—deleting files, force pushing, writing to system directories, or pushing commits containing secrets. Full automation without guardrails is equivalent to handing over the development environment to a system that is "smart most of the time, and occasionally makes mistakes."
The question isn't "Should we grant full access?"—it's "Once we do, who will stop that occasional incident?"
Scattered Shell Scripts Can Get Out of Control
Next, we started adding hooks to Claude Code. At first, it was just a few shell scripts to block dangerous commands, but three months later, it blew up in our face: scripts were scattered everywhere, a single crash brought everything to a halt, and there was no kill switch to turn it off.
The scripts are scattered all over the place
A dozen or so .sh files—some are in the `hooks` directory, some are in the `scripts` directory, and some are in the `home` directory. Each one duplicates the implementation of JSON parsing and error handling.
One Dispatcher to Manage All Hooks
In version v0.1.0, we've consolidated all hooks into a modular framework: a dispatcher receives all events and routes them to the appropriate handlers based on the configuration file.
Two-Phase Dispatch is a key design element. Security-related handlers (such as blocking rm -rf) are marked "critical"; these are always executed first and are not subject to time limits. Other handlers share a 5-second time limit—if they time out, they are skipped, but they do not block the AI workflow.
Preventing Dangerous AI Operations
In the event of an incident like the one where a colleague accidentally deleted an entire project folder, these three handlers are the ones that step in—they are marked as "critical," are executed unconditionally as a top priority, and handle the most critical security issues.
Block rm -rf /, sudo, git push --force, chmod 777 and other dangerous commands. Using a quote-aware shell parser to parse commands prevents them from being bypassed by quotes or sub-shells.
At git push, it pre-scans diffs to intercept AWS keys, GitHub tokens, PEM private keys, and more. Triggers only on pushes, without interfering with local development. Supports # nosec for line-by-line exemption.
Prevent prompt injection attacks. Detect injection statements such as "ignore previous instructions," dangerouslyDisableSandbox Elevation of privileges operations, etc., curl Theft of external data, etc. Three types of threats; independent scanning.
"If bash_safety had been in place at the time, the first colleague's folder wouldn't have been deleted. That was the first handler we wrote, and it marked the beginning of the entire project."
Let AI Check Its Own Work
Be sure to run the validation before committing; that's the rule set by the four handlers of the quality gate. Before that, whether validation ran was entirely up to the AI—after writing code, it would often commit directly and skip validation.
Interception git commit and gh pr create. Require the AI to first complete validation (lint, type check); once it passes, it must write a marker file before the commit is approved. One-time token: Expires after use.
When the sub-agent completes the task, automatically run lint and typecheck. If it fails, send the agent back to fix it. Maximum 5 loops and a 30-minute limit—let it pass if exceeded to avoid getting stuck.
After AI edits a file, it automatically runs ruff (Python) or biome (JS/TS) for formatting. Never blocks—even if the formatter crashes, it silently lets it pass. Code uniformity is good, but not worth interrupting the workflow.
At the end of a Claude Code session, check for uncommitted code changes. By default, it only issues a reminder (non-blocking); manual opt-in is required to actually block. This prevents the AI from finishing its task and leaving changes behind in the working tree.
Preventing AI from Losing Its Memory
A sub-agent is born blank, and the context of the main conversation does not carry over automatically. The three handlers managed by Context-management are precisely meant to bridge this gap—since an AI agent's own context window is also limited, failing to remember well is equivalent to starting over every single time.
When the sub-agent starts up, it automatically injects the project files. Read .context/ In the JSONL configuration file under the directory, each agent type can have different injection content. The limit is 8,000 characters; avoid overfilling the context.
Memory relay between sessions. Handover documents written before the end of the previous session are automatically injected when a new session starts. 5-minute TTL to prevent stale data interference.
When AI exits Plan Mode, remind it to save the plan to memory before starting implementation. Since Plan Mode itself consumes a large amount of context, not saving it would be a waste. A one-time reminder; no repeats.
See What AI Is Doing
When there are too many sub-agents, you can't see what's going on in the background—that's where these four handlers come in to restore visibility.
Force every sub-agent to have a descriptive name. Block it if no name is given. At the same time, suggest a suitable agent type based on the prompt content—use worker for coding, explorer for researching, and writer for documentation.
Each hook event writes a line of JSONL to the spool file. Latency is <1ms, with no impact on any operations. A background process batch-writes to the database every 2 seconds for dashboard queries. Zero network latency across the entire link.
When AI executes a service restart or Docker operation, it notifies the monitoring system to enter a maintenance window. Automatically exit the maintenance mode once the operation is complete. This lets the monitoring system know that "this downtime is expected," preventing false alarms.
When AI finishes its work, it notifies you via TTS voice. No need to stare at the screen waiting—go make some coffee, and it will call you when it's done. It will also use voice prompts when it needs you to answer questions. A seven-layer guard mechanism filters out the intermediate states of sub-agents, speaking up only when the task is truly complete.
Three Principles That Apply to All Handlers
Block-at-Submit, not Block-at-Write
The AI doesn't block when writing files. It only blocks when it's about to commit or push.
Reason: The AI's workflow is continuous. If it gets blocked during the "Edit" phase, it loses context and doesn't know where it left off. It makes more sense to review the output once it's ready to be submitted.
Fail-Open, Forever
Each handler is wrapped in try/except. Handler crashed? Let it through. Failed to read the config? Let it through.
Reason: A guardrail shouldn't be more dangerous than what it's meant to protect. A crash handler shouldn't bring the entire AI workflow to a halt.
Every rule has a loophole
secret_scan is present # nosec, `verify_commit` has a marker mechanism, and `bash_safety` has an environment variable toggle.
Reason: Rules with no escape clauses will eventually be shut down entirely. Providing users with a way to work around them actually helps the rules last longer.
What matters more than what you’ve done is what you haven’t done.
- Do not intercept Edit / Write. Do not interfere when the AI is writing files. Interrupting the AI's writing process midway will result in an incomplete file state. All reviews are deferred until commit or push.
- Do not run automated tests inside the gate. verify_completion only runs lint and typecheck, deliberately excluding tests. Tests are too slow, have side effects, and may alter database status. Putting them in the automated gate carries more risk than reward.
- Does not automatically repair; it only blocks and explains. bash_safety won't help the AI rewrite "safer commands." It only says "this command was blocked because of X." Let the AI decide its next step. Automatic rewriting easily introduces even weirder bugs.
- Do not assign a context budget score. We wrote a context_supervisor handler (800 lines) that tried to calculate context usage and suggest compression timing. The concept was good, but the scoring model was inaccurate—the "importance" of context cannot be determined using heuristic rules. Disabled.
- Not Fail-Closed. All handler crashes are allowed through. Someone might ask, "Isn't this dangerous?"—what's more dangerous is the guardrail itself locking up the entire system. The AI stops working, and the user has no idea why.
hook-observatory
Licensed by MIT, with no external dependencies; install and use right away.
Three installation methods: Homebrew, Git Clone, or desktop installer.
★ GitHub ↓ Download the Installer
Recommended for macOS. Done in two lines—no need to clone.
brew install hook-observatory
# Install on Claude Code
hook-observatory
Works on all platforms. Simply run the installation script.
cd hook-observatory
Python 3 install.py
GUI-guided installation: Check the "handler" box and set the tool path.
When opening macOS for the first time, you must run: xattr -cr Installer.app
Post a few lines of code to see which ones get blocked
The "bash_safety" card lists four categories of commands: rm -rf, sudo, git push --force, chmod 777. Paste a few lines of commands you've run yourself. I'll compare them line by line against those four categories and point out which ones would trigger `verify_commit` or `secret_scan`. Everything runs entirely in your browser—nothing is sent out, and nothing is executed. Just paste a few lines, no need for your entire history—leave out the lines containing secrets, internal hostnames, or private paths.
The sources for all constants are in this article: The four types of dangerous commands and "parsing commands using a quote-aware shell parser will not be bypassed by quotes or subshells" come from the bash_safety card; git commit / gh pr create: lint and type checking must be completed before approval, as stated on the "verify_commit" card; git push: scanning the diff beforehand comes from the secret_scan card. Four things this cannot do here: ① It evaluates the string you pasted, not what actually happens on your machine—nothing is sent out, and nothing is executed. ② This is an approximation based on the four categories named in the text, not the actual rules of hook-observatory; the text mentions "and other dangerous commands," meaning that list is inherently incomplete, so "not being highlighted in red" does not equal safe. ③ The third critical handler in the text (skill_security) is attached to PreToolUse · Write/Edit, which looks at file contents rather than the command line, so it cannot be checked here. ④ As per the "intentionally not doing" section in the text, getting blocked will only tell you the reason, and will not provide you with a "safer way to write it."
Copy this to your AI Agent
The question isn't "whether or not to grant full access," but "who will stop that occasional incident once access is granted."—prompting "don't delete important files" is useless; AI is probabilistic and will flip out during some inference; hooks are deterministic, and `rm -rf /` is blocked forever. The starting point of this piece is the very first colleague whose entire project folder was deleted by AI.
Send the text below to your AI, have it first check whether you're running around naked, and then fill in the gaps according to the checklist in this article:
Further Reading
Blake Crosley's article, "Why Each of My 95 Hooks Exists," made me realize that each handler should correspond to a real-world scenario.
| Resources | Why It Matters |
|---|---|
| operonlab/hook-observatory | The open-source projects discussed in this article |
| Blake Crosley: Why Each of My 95 Hooks Exists | An "incident-driven" approach to building hooks. Every hook is based on an incident. |
| GitButler: Automate Your AI Workflows with Claude Code Hooks | One of the earliest practical examples of hooks, which inspired our initial design |
| aiorg.dev: The Complete Guide to Claude Code Hooks | The "certainty vs. probability" framework accurately captures the value proposition of hooks. |
| Jackle: Claude Code Voice Mode | An implementation reference for voice notifications using hooks, which inspired the design of the `voice_notify` handler |