Last Tuesday at 3am, a multi-agent loop hit 12K KV writes/minute and froze. The loop was a one-line counter bug. That part was fixable. What I found while tracing it was worse.
I had --dangerously-skip-permissions
enabled on a Claude Code session that was running D1 migrations. I thought it was pointing at staging. It wasn't — I'd misconfigured my env file reference, .env.production
instead of .dev.vars
. Claude didn't ask. The flag told it not to. The migration was ADD COLUMN
, not DROP COLUMN
, so no data loss. Survivable. But only barely.
The thing I got wrong: I treated --dangerously-skip-permissions
as "skip the annoying confirmation popups." It's actually "remove the only moment a human sees what command is about to run." Those are very different things. Turning the flag back off helps, but it doesn't constrain what Claude attempts — it just adds a prompt you'll click through anyway at 3am.
What actually worked was adding a deny rule in .claude/settings.json
:
{
"permissions": {
"allow": ["Bash(wrangler d1 execute * --local*)"],
"deny": ["Bash(wrangler d1 execute *)"]
}
}
The allow rule is more specific than the deny, so --local
calls go through and everything else is blocked before execution. Over 2 weeks post-fix, Claude attempted zero production DB commands. Three deny events were logged — all from ambiguous prompts I wrote during fast context-switches, not from Claude going rogue.
I ended up running three layers: the settings.json
allowlist, a separate git worktree for migration work that physically contains only staging credentials, and a CLAUDE.md
that instructs Claude to ask before anything touching production. The CLAUDE.md approach has a real caveat though — in long sessions the instructions lose weight as context grows. Anything critical needs to be restated in the prompt itself.
I wrote up the full breakdown — including the worktree setup, the exact CLAUDE.md
wording, and why MCP tool permissions behave inconsistently with the deny rules — over on riversealab.com.