●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Installing Safety Valves in Claude Agents: A Three-Layer Kill-Switch Design for Solo Operators
A design record from a real production incident — three hours of runaway retries that cost $32 — that led me to rebuild every Claude agent with a three-layer kill switch: in-process guards, platform-level kill flags on Cloudflare KV, and an observer worker that catches the warning signs within three minutes. Working TypeScript and operational metrics included.
January 2026, 2:47 a.m. My phone buzzed — not with a crash report, but with a Stripe charge notification. Two hundred times the usual amount. When I opened the Anthropic console, a single wallpaper-classification worker had fired 12,000 requests in about three hours. The cause was a quiet, recursive retry loop: classification fails, regenerate, fails, regenerate, all night, no warnings.
I've been running indie AdMob-style apps since 2014. Over eleven years and 50 million cumulative downloads I've seen plenty of server-side incidents. But once I started threading Claude agents into production flows, one thing became uncomfortably clear: probabilistic failures retried by probabilistic systems can burn money in a single night if you're not careful. App crashes are visible. An agent that simply "keeps running" is silent, and you notice late.
That night's bill came to about $32. The dollar figure mattered less than the realization that my setup was structured in a way I couldn't notice the fire. The next week, I rebuilt every Claude agent I run with what I now call a three-layer kill switch: in-process guards, an external kill flag, and an observation layer — three places that can stop the agent independently. In the four months since, runaway incidents: zero.
Why Solo-Operated Agents Need Kill Switches More, Not Less
Cloud vendors' auto-scaling and "budget alerts" are designed for teams. As a one-person operator running multiple agents, by the time the warning email lands it's usually too late. Anthropic's console budget alerts roll up on a roughly 24-hour delay. Cloudflare Workers' billing alerts hit you the following month. By the time you notice, the charges are already locked in.
Claude agents are also built to "keep going if anything is salvageable." That's a strength, but it lives next door to "retry loops that quietly never end." Working through this in production, I came to believe that safety for an agent has to be structural, not a feature. A single flag or a single try/catch isn't enough. You need three different places that can stop it — inside the process, outside the process, and a layer that watches both. Skip any one, and something eventually slips through.
My grandfather was a temple carpenter, and he used to say "always leave room for repair from the start." The slight play in old shrine pillars is intentional — it absorbs movement. I think of kill switches the same way: deliberate slack inside the gaps of the happy path.
The Three Layers at a Glance
The design uses three layers. Each can stop the agent independently; lower layers still catch what the upper ones miss.
Layer
Where
Response time
What it protects
Layer 1: In-process guards
Inside the agent code
Immediate (per request)
Total run volume (iterations, tokens, wall-clock)
Layer 2: Platform kill flag
KV / config store
Seconds to ~30s
An external "stop now" channel
Layer 3: Observation layer
Separate worker / log sink
Within 3 minutes
Catching warning signs and alerting
Layer 1 alone fails when a code bug slips past the guards. Layer 2 alone is useless if no one notices in time. Layer 3 alone has no stop mechanism. The point is having all three.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A drop-in SafetyValve class that caps iterations, tokens, wall-clock time, and per-request max_tokens together
✦A KV-based external kill flag with a deliberate cacheTtl trade-off explained, so you can stop a runaway agent from your phone
✦Three actual failure modes I hit after deploying the layers, including the most insidious one — the silently healthy observer
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Layer 1: In-Process Guards — The Agent Stops Itself
The first layer lives inside the agent. Assuming Cloudflare Workers + TypeScript, here's a reusable SafetyValve class you can drop in.
// safety-valve.ts — Layer 1: in-process guardsexport type ValveLimits = { maxIterations: number; // retries/iterations allowed per invocation maxOutputTokens: number; // total tokens per invocation maxWallClockMs: number; // total wall-clock time per invocation perRequestTokenCap: number; // upper bound on a single request's max_tokens};export class SafetyValve { private iterations = 0; private tokensSpent = 0; private readonly startedAt = Date.now(); constructor(private readonly limits: ValveLimits) {} beginIteration(): void { this.iterations += 1; if (this.iterations > this.limits.maxIterations) { throw new SafetyTripped("iteration_cap", this.iterations); } if (Date.now() - this.startedAt > this.limits.maxWallClockMs) { throw new SafetyTripped("wall_clock_cap", Date.now() - this.startedAt); } } spendTokens(input: number, output: number): void { this.tokensSpent += input + output; if (this.tokensSpent > this.limits.maxOutputTokens) { throw new SafetyTripped("token_cap", this.tokensSpent); } } capRequestTokens(requested: number): number { return Math.min(requested, this.limits.perRequestTokenCap); }}export class SafetyTripped extends Error { constructor(public readonly reason: string, public readonly value: number) { super(`SafetyValve tripped: ${reason}=${value}`); }}
Instantiate this once per agent invocation. The important detail is watching iterations, tokens, and time simultaneously. My runaway night had small per-request retry counts. But the total tokens had climbed to about 200x the expected ceiling. Any single metric alone fails to catch the runaway — that's the lesson I take with me from operating these in production.
Then call beginIteration() at the top of every loop:
// wallpaper-classifier.ts — the agent sideconst valve = new SafetyValve({ maxIterations: 8, // up to 8 retries per image maxOutputTokens: 6_000, // cap total tokens maxWallClockMs: 60_000, // 60-second hard stop perRequestTokenCap: 512,});while (true) { valve.beginIteration(); const res = await client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: valve.capRequestTokens(800), system: SYSTEM_PROMPT, messages, }); valve.spendTokens(res.usage.input_tokens, res.usage.output_tokens); const parsed = trySafeParse(res); if (parsed.ok) return parsed.data; messages.push({ role: "assistant", content: res.content }); messages.push({ role: "user", content: parsed.repairHint });}
valve.capRequestTokens(800) matters more than it looks. The trigger for my runaway was the agent deciding "maybe writing longer will help" and bumping max_tokens request by request. Capping individual requests too prevents the exponential bloat per loop iteration.
Layer 2: Platform Kill Flag — A Way to Stop the Agent from Outside
Layer 2 assumes Layer 1 might fail due to a code bug. You put a "stop now" key somewhere outside the agent — I use Cloudflare KV — and read it before each request.
// kill-switch.ts — Layer 2: KV-based kill flagexport type KillSwitchEnv = { KILL_SWITCH: KVNamespace;};const KEY = "agent:wallpaper-classifier:kill";export async function isKilled(env: KillSwitchEnv): Promise<boolean> { // cacheTtl avoids excessive KV reads (a few seconds stale is fine) const v = await env.KILL_SWITCH.get(KEY, { cacheTtl: 10 }); return v === "1";}export async function killAgent(env: KillSwitchEnv, ttlSeconds = 3600) { await env.KILL_SWITCH.put(KEY, "1", { expirationTtl: ttlSeconds });}export async function reviveAgent(env: KillSwitchEnv) { await env.KILL_SWITCH.delete(KEY);}
The cacheTtl: 10 is deliberate. Reading KV strictly per request costs more than Layer 1 ever does. Allowing 10 seconds of staleness means a runaway gets stopped within 10 seconds in the worst case, while KV read cost drops by an order of magnitude or more.
Then check isKilled at the loop head:
while (true) { if (await isKilled(env)) { throw new SafetyTripped("external_kill", 1); } valve.beginIteration(); // ... normal flow}
For "from outside," I exposed a one-liner admin Cloudflare Worker. Hitting https://admin.example.com/kill/wallpaper-classifier from my phone or laptop quiets the agent within 10 seconds. Being able to stop a runaway from your phone on a train when "that charge notification looks off" is genuinely valuable in solo operation.
Layer 2 saved me once when Cloudflare Queues retries were silently amplifying inside the Queue → Worker → Anthropic API path. A bug in my worker was re-enqueuing instead of routing to dead-letter. The moment I flipped the kill flag, the worker started skipping straight to dead-letter, and I could focus on root-causing without bleeding money. Layer 1 alone can't help here — the agent is technically "running correctly" and won't stop itself.
Layer 3: The Observation Layer — Spotting Trouble Within Three Minutes
The third layer is about noticing, not stopping. Even with Layers 1 and 2 in place, none of it helps if you don't know it's time to flip the switch.
Run the observer in a separate worker. Co-locating it with the agent introduces an unhappy coupling: if the agent stalls, your observer might stall with it.
Three things matter. First, per-minute buckets across a 5-minute window. Per-hour is too slow; real-time is too noisy. Five minutes is my empirically sweet spot. Second, separate the alert threshold from the auto-stop threshold. Notify early; auto-stop only when you're confident. False auto-stops have their own operational cost. Third, the observer itself runs on Cloudflare Cron Triggers every minute. A long-lived process can stall silently. Cron "wakes up fresh" each minute, which is itself a kind of liveness signal.
I send notifications to Slack. During audio recordings, I can't always see the screen, so I keep distinct notification sounds for these channels.
Three Failure Patterns I Hit After Deploying the Design
After four months running this design, three holes showed up. Sharing them might save you a Saturday.
The first incident happened even after Layer 1 was in place. The agent itself had an iteration cap — but the upstream queue worker had its own retry policy. When the agent threw SafetyTripped, the queue saw it as a normal failure and re-enqueued the same job. Layer 1's iteration cap was being defeated by a layer above it.
The lesson: never treat SafetyTripped as a retryable error. Catch it, ship to dead-letter, mark it for human review. In my current code, the error type carries a retryable: false flag explicitly.
Pattern B: The Kill Flag That Never Arrives (Stale Cache)
When I built Layer 2 the first time, I set cacheTtl: 60. A minute felt fine. In operation, one minute is forever during a runaway. At 200 requests/minute, that's about $0.4 burned before it stops. The cacheTtl value is "how long it takes to stop." I now use 10 seconds.
Setting cacheTtl: 0 does work, but KV read costs become visible — for my scale it added roughly $3/month. The 10-second value landed where "worst-case acceptable delay" met "acceptable KV cost," which I think is the honest framing for a solo operator.
Pattern C: The Silently Healthy Observer
The third failure: the observer worker was failing internally. The Cron was firing, but telemetry collection was throwing and checkWindow was operating on empty data. Thresholds never tripped. No alerts. From outside, nothing looked wrong because nothing was being looked at — the worst possible state.
The fix: the observer sends a 5-minute heartbeat to a separate, normally-muted Slack channel. I mute the channel, but if the heartbeat goes silent, I notice "huh, it's been too quiet." Observing the silence itself — a meta-layer.
A Gradual Adoption Path
"Doing all of this at once" sounds unrealistic, and it was for me too. I built this up in stages. If you're starting today, this order feels sustainable:
Layer 1 with maxIterations and maxWallClockMs only: about half a day of work, prevents roughly 80% of worst-case incidents
Add the Layer 2 KV kill flag: one admin URL. The peace of mind from being able to stop it from your phone is large
Add Layer 1 token cap: at this point you've eliminated about 95% of solo-operator risk
Then build Layer 3: Cron + Slack. Detection comes after a stop mechanism exists — order matters
In my experience, once Layer 2 was in, I started sleeping properly again. Even without Layer 3, the worst case is "wake up, notice, stop from phone" — which keeps incidents below $30. Layer 3 shortens "time-to-notice"; it's an optimization, not the foundation.
If You're Just Starting
Solo-operated Claude agents are structurally more fragile than enterprise production. There's exactly one person observing them, and they keep running while that person sleeps. Safety has to be structural, not just functional.
"Can today's agent stop itself safely while I'm asleep?" — that question, answered immediately and confidently with "yes," is what health looks like for me now. If you're operating Claude agents on your own, I hope some of this is useful. Thanks for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.