Agentic AI
I Connected Claude to My Terminal and Broke My Laptop (Lessons on Agent Guardrails)
A few weekends ago I gave a Claude agent direct access to my terminal — a run_bash tool, no restrictions, just "let it clean up my messy Downloads folder." Within ten minutes it had done something I didn't ask for, in a directory I didn't expect, and I spent the next hour figuring out what happened and why. This post is the honest version of that story, and the three specific guardrails that would have prevented every part of it.
The setup, and the mistake baked into it
The tool looked innocent:
import subprocess
def run_bash(command: str) -> str:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.stdout + result.stderr
tools = [{
"name": "run_bash",
"description": "Run a bash command and return its output.",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
},
}]Nothing about this description tells Claude what's safe to run or what to avoid. It just says "run a bash command." I asked it to organize my Downloads folder — move PDFs here, screenshots there, installers somewhere else. Reasonable enough. But partway through, it hit a folder with a broken symlink, tried a find command to investigate, and that command's output looked enough like an error that its next attempt to "fix" the situation touched a directory well outside Downloads. Nothing catastrophic — some misplaced files, one directory I had to manually restore from backup — but entirely avoidable, and entirely my fault for handing over a tool with no boundaries.
Mistake #1: no scope on what the tool can touch
The fix isn't "trust the model to behave" — it's "make misbehaving impossible at the code level." A tool should refuse to act outside a boundary you define, not rely on Claude choosing not to wander:
import subprocess
from pathlib import Path
ALLOWED_DIR = Path("/Users/pranshu/Downloads").resolve()
def run_bash(command: str) -> str:
# crude but effective: block anything that references a path outside the sandbox
if ".." in command or command.strip().startswith("/"):
return "Blocked: command references a path outside the allowed directory."
result = subprocess.run(
command, shell=True, capture_output=True, text=True,
cwd=ALLOWED_DIR, timeout=10,
)
return result.stdout + result.stderrThis is still crude — a determined bad actor could work around a string check like this — but it's not defending against an adversary. It's defending against an agent that's confused, not malicious, which is the far more common failure mode in practice.
Mistake #2: no confirmation before anything destructive
The tool ran every command it was asked to run, including ones that delete or move things, with zero human checkpoint. The fix: classify commands, and hold the risky ones for approval.
DESTRUCTIVE_COMMANDS = ["rm", "mv", "dd", ">"]
def run_bash(command: str) -> str:
if any(danger in command for danger in DESTRUCTIVE_COMMANDS):
approved = input(f"Claude wants to run: {command}\nAllow? (y/n): ")
if approved.lower() != "y":
return "Command blocked by user."
result = subprocess.run(command, shell=True, capture_output=True, text=True, cwd=ALLOWED_DIR)
return result.stdout + result.stderrThis one change — a human in the loop for anything that deletes or overwrites — would have stopped the entire incident at the exact moment it started going wrong, and cost nothing in terms of how useful the agent still is for everything non-destructive.
Mistake #3: no limit on how long it could run unsupervised
Remember the loop from the previous post — Claude keeps calling tools until it decides it's done, or until max_turns runs out. I hadn't set one. A confused agent with no turn limit and no destructive-action gate doesn't just make one mistake — it keeps going, compounding it, because nothing tells it to stop and check in.
def run_agent(task: str, max_turns: int = 8):
for turn in range(max_turns):
# ... the loop from the previous post ...
pass
return "Reached the turn limit — stopping for a manual check."A hard ceiling isn't just a cost control. It's a forced checkpoint: if a task genuinely needs more than eight tool calls to finish, that's exactly the moment a human should look at what's happened so far, not the moment to quietly raise the limit and walk away.
The actual lesson
None of these three fixes require distrusting the model's intelligence — Claude did exactly what a reasonable interpretation of a vague, unrestricted tool allowed it to do. The failure was entirely in the tool design: no scope, no approval gate, no ceiling. That's true of every agent incident I've read about since, not just mine — the model followed instructions; the tool just didn't have a fence around it.
The guardrail checklist, if you're building one of these
- Scope every tool to the smallest area it needs — a specific directory, a specific API, a specific table, never "anything."
- Gate destructive actions behind human confirmation — delete, overwrite, send, deploy, pay: anything irreversible gets a checkpoint.
- Always set a turn limit, and treat hitting it as a signal to review, not an inconvenience to raise.
- Log every tool call — command, input, output, timestamp — so when something does go sideways, you can actually see what happened instead of guessing.
Reference links
- Claude tool use — troubleshooting and safe design: platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- Claude's bash tool (Anthropic's own sandboxed version of this exact idea): platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool
- MCP security guidance (same principles, for MCP servers specifically): modelcontextprotocol.io/specification/latest
Next in this series: "Multi-Agent Systems Explained — When One Claude Isn't Enough."
Related articles
Agentic AI
5 Agent Mistakes I Made So You Don't Have To
Every one of these cost me real debugging time. Each has a one-line fix — here they are, with the code.
- #claude
- #agentic-ai
- #python
- #debugging
- #best-practices
Agentic AI
How Much Does an Agentic Claude Workflow Actually Cost? (Real Numbers)
Almost nobody publishes real token math for multi-step agents. Here's a worked example — a 6-turn agent loop, priced out — plus a small calculator function you can reuse.
- #claude
- #agentic-ai
- #pricing
- #python
- #cost-optimization
Agentic AI
I Gave Claude Hands: My First Tool-Use Agent in 20 Lines
Agents sound complicated until you see the actual request-response loop underneath. Here's the whole thing, stripped down to one tool and 20 lines of Python.
- #claude
- #agentic-ai
- #tool-use
- #python
- #anthropic