Observed since 20 Oct, 23:19 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Deterministic safety hooks for Claude Code
A few months ago, a developer asked Claude to clean up an old repo.
Claude ran rm -rf tests/ patches/ plan/ ~/.
That trailing ~/ expands to the home directory. It wiped years of files on their Mac. The post hit 1,500+ upvotes on r/ClaudeAI within hours — because everyone building with agents recognized exactly how it happened: not malice, just confidence with no checkpoint.
It's not an isolated story:
git reset --hard, wiping hours of uncommitted work, right after telling the user the operation was "safe."Same root cause every time: the model's confidence and the actual safety of the action are two different variables, and nothing was checking the second one.
So I built the boundary myself. Excited to share Sensorium — deterministic safety hooks for Claude Code. 👇
LLM agents are incredible at writing code. They are also, occasionally, incredible at:
rm -rf with a trailing ~/ nobody meant to includegit reset --hard on your uncommitted work, confidentlycurl -X DELETE-ing a resource because the docs made it sound safeNone of this is malice. It's confidence without a checkpoint. And "just review every diff" doesn't scale when the agent is running fifty tool calls a session.
You don't need a second LLM watching the first one. You need sensors — small, deterministic, boring rules that wake up on a specific kind of change, check exactly what they care about, and say yes/no/wait.
No vibes. No judgment calls. Pattern matching and policy, all the way down.
Claude wants to use a tool
→ PreToolUse hook
→ Sensorium reads the tool input
→ sensors match
→ allow / block / warn
→ tool executes (or doesn't)
→ PostToolUse hook
→ Sensorium checks the resulting file content
→ writes an audit ledger
| Protects against | How | Gate |
|---|---|---|
rm -rf /, rm -rf ~, dd ... of=/dev/sd* | filesystem.wipe | unconditional block |
| Bulk delete without a backup | filesystem.bulk_delete | needs backup_exists + dry_run_passed |
git reset --hard, git clean -fxd | git.destructive | needs backup_exists |
curl -X POST/PUT/DELETE/PATCH | external_api.write | needs a snapshot + rollback plan |
kubectl apply/delete, terraform apply/destroy, aws ... delete | infra.mutation | needs snapshot + dry-run + rollback |
Direct psql/mysql writes | db.write | needs snapshot + rollback plan |
| Reapplying a stale archive/cache as truth | data.apply_from_archive | unconditional block |
| Skipped tests slipped in quietly | test_skip_introduced | warn, shows up in the audit report |
--dry-run on the command bypasses the gate — sensors check for it explicitly.
This is the part I actually wanted to ship. Your project has opinions Sensorium can't guess — so tell it:
# .sensorium/sensors.yaml
sensors:
- name: no_force_push
description: Block git push --force (plain push still allowed)
tools: [Bash]
action: block
patterns:
- 'git\s+push\b.*(--force\b|-f\b)'
unless:
- '--dry-run'
message: |
Blocked: force-push detected. Use --force-with-lease and confirm
with a human first.
Drop it in, no restart, no config reload — the next tool call picks it up.
Sensor fields:
tools — which Claude Code tools trigger this sensor (Bash, Edit, Write, MultiEdit)on_file_change — glob patterns for file paths (PostToolUse, checks content)action — block (exit 2, Claude sees the message) or warn (logged, shown in report)patterns — regex list matched against the Bash command or file pathunless — if any of these match, the sensor does not triggerblock_if_contains — regex matched against file content after an editrequire_contains — regex that must be present in file content (absence triggers the sensor)message — shown to Claude when blocked or warnedStep 1 — the CLI
pipx install agent-sensorium
# or: pip install agent-sensorium
Step 2 — wire up Claude Code
cd my-project
sensorium init claude-code # this project only
sensorium init claude-code --global # every project
Writes .claude/settings.json with the absolute path to the sensorium binary. Claude Code picks it up immediately.
Step 3 (optional) — your own rules
mkdir -p .sensorium
# add .sensorium/sensors.yaml, see above
That's it. Claude Code works exactly as before — Sensorium just quietly rides along on every tool call.
sensorium report # show session audit log
sensorium report --clear # show and reset
=== Sensorium Audit Report ===
Tools used: 12
Sensors triggered: 3
Blocked actions: 2
File violations: 1
--- Blocked Actions ---
2026-07-03T10:14:22 [filesystem.wipe] Bash: rm -rf /tmp/old-data
2026-07-03T10:17:05 [git.destructive] Bash: git reset --hard origin/main
--- File Sensor Violations ---
2026-07-03T10:19:11 [full_object_overwrite] src/apply.js
required: ['delta|changed_fields', 'precondition|current_hash']
--- Sensor Activity ---
external_api.write: 3x
filesystem.wipe: 2x
Every block, every warning, every proof registered — append-only, in .sensorium/state.jsonl. Nothing silently disappears.
Sensorium follows the State-Delta pattern:
world change (Claude tool use)
→ typed delta/event (PreToolUse / PostToolUse)
→ matching sensors wake up
→ each sensor selects the narrow context it needs
→ deterministic policy evaluates
→ allow / block / warn
→ ledger records the fact
No broad rescans. No LLM judge. No polling. A sensor declares exactly what it listens for and what invariant it protects — that's the whole contract.
.sensorium/
state.jsonl # append-only event ledger
snapshots/ # file snapshots before edits (content-addressed)
sensors.yaml # your project-specific rules (optional)
This is regex and policy, not a sandbox. It catches the direct, literal case — an agent typing a dangerous command in the open. It is not a defense against something actively trying to route around it (a script file, an interpreter, a clever quote). Self-reported proofs are exactly that — self-reported. Treat it as a seatbelt, not a cage, and you'll use it correctly.
sensorium init claude-code writes this to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook pretool" }]
}
],
"PostToolUse": [
{
"matcher": "Bash|Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook posttool" }]
}
],
"Stop": [
{
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook stop" }]
}
]
}
}
Exit code 2 from pretool blocks the tool. Exit 0 allows it.
AGPL-3.0. See LICENSE.
Not hypotheticals — these happened, and each one maps directly to a sensor above:
rm -rf ~/ Incident — Docker's write-up of the r/ClaudeAI postIf you're shipping agents with real filesystem/shell/API access and you're not doing this yet — you're one confidently-wrong tool call away from a bad afternoon.
Would love thoughts from anyone else building guardrails for agentic coding tools. 🙏
#AIagents #ClaudeCode #DeveloperTools #AgentSafety #OpenSource
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 2 Jul 2026 | 2 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:19 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:db53f55bb75b564dd90380260bbc6fc23c64a29c865bd06f455a414e617054a8