← Blog

That day, the AI decided to “clean up,” and the project was gone.

· 15 min read · 334 views
AI-Assisted Development Hooks Guardrails Context Management Observability Open Source
Practical AI Engineering

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.

Event

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.

$ rm -rf ./project-name
# 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.

Reality

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 Code — Skip all permission prompts
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?"

Question

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.

1

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.

2

One crash brings everything to a standstill

A hook crashed due to invalid JSON formatting. Claude Code's hook mechanism is synchronous: If one hook crashes, the entire pipeline is interrupted.

3

No on/off switch

Want to temporarily disable a hook? You'll need to go to settings.json I make the changes manually. After testing them, I often forget to revert them.

Solution

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.

34
Handler Module
10
Comprehensive Coverage of Event Types
1
YAML Configuration File
0
External Dependencies
settings.json
Claude Code default configuration. 10 event types, each pointing to the same dispatcher
Claude Code
dispatcher.py
Shim reads the event type and JSON input, then calls the handler registry
Entry Point
Two-Phase
Critical handlers run first (with no time limit), while deferrable handlers are executed sequentially within a 5-second time limit.
Scheduling Strategies
config.yaml
Each handler can be enabled or disabled independently. Toggle it with a single line of YAML—no need to edit settings.json.
Configuration Layer

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.

Safety Guardrail

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.

bash_safety
PreToolUse · Bash

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.

secret_scan
PreToolUse · Bash

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.

skill_security
PreToolUse · Write/Edit

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."

Quality Control

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.

verify_commit
PreToolUse · Bash

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.

verify_completion
SubagentStop

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.

auto_format
PostToolUse · Edit/Write

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.

review_gate
Stop

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.

Context Management

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.

context_inject
SubagentStart

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.

context_relay
SessionStart · PreCompact

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.

plan_impl_gate
PostToolUse · ExitPlanMode

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.

Management and Monitoring

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.

agent_naming
PreToolUse · Agent

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.

observability
All Events

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.

sentinel_notify
PreToolUse · PostToolUse · Bash

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.

voice_notify
Stop · AskUserQuestion

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.

Design Philosophy

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.

Deliberately not doing it

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.
Open-Source Projects

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

Homebrew

Recommended for macOS. Done in two lines—no need to clone.

brew tap operonlab/tap
brew install hook-observatory

# Install on Claude Code
hook-observatory
Git Clone

Works on all platforms. Simply run the installation script.

git clone https://github.com/operonlab/hook-observatory.git
cd hook-observatory
Python 3 install.py
Installer

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

.dmg Apple Silicon .dmg Intel Mac .exe Windows

Tools

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 section

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:

I am reading an article titled "The Day AI Decided to 'Clean Up' and the Project Was Gone" (https://blog.joneshong.com/en/blog/hook-observatory). The author's colleague had an entire project deleted by an AI executing `rm -rf`, prompting them to build a hook guardrail: A dispatcher + categorized handlers, where safety-related items are marked critical and always run first (Two-Phase). Please first check my current actual status, then help me implement this setup accordingly. Do not give me a generic checklist: First, check if I am running completely unprotected ("running naked"): 1. Which CLI am I using? Am I running with full permissions enabled (such as `claude --dangerously-skip-permissions`, `codex full-auto`, `gemini --sandbox=false`, etc.)? 2. Do I currently have any hooks / pre-commit set up? Check my `settings.json`, `hooks` directory, and `.git/hooks` to see if anything exists. 3. Have any dangerous commands appeared in my recent shell history without being blocked? Next, based on the handler categories in the article, determine which ones I should implement first: - `bash_safety`: Block `rm -rf /`, `sudo`, `git push --force`, `chmod 777` (must be quote-aware to prevent bypasses via quotes or subshells). - `secret_scan`: Scan diffs for AWS Keys / GitHub Tokens / PEM private keys before pushing. - `skill_security`: Block prompt injection, `dangerouslyDisableSandbox`, and outbound `curl` requests. - `verify_commit`: Force linting / typechecking to pass before allowing a commit. Reply to me with: Sorted by "how painful it would be if things go wrong," which 3 handlers should be written first, and how the escape hatch (e.g., line-by-line exemptions) should be designed for each. If I am not running with full permissions at all and am manually clicking "allow" for every command, tell me straight up that I might not need this setup yet.
References

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
✦ Copy Prompt