{"slug": "give-claude-code-real-autonomy-without-letting-it-wreck-your-system", "title": "Give Claude Code real autonomy without letting it wreck your system", "summary": "Developer Harry Philippe Mbouyap has released an open-source project that provides a three-layer safety system for giving Claude Code real autonomy without risking system damage. The approach combines permission settings, a pre-tool hook that blocks dangerous commands like recursive force deletes and force pushes, and a fail-closed mode for unattended runs. The code is available on GitHub under an MIT license.", "body_md": "Author: Harry Philippe Mbouyap. All the code below is MIT and lives in a small repo you can\n\nclone and run:[https://github.com/hbouyap/claude-code-safe-automation]\n\nGetting an AI agent to *write* code is a solved problem. The part that keeps teams up at night\n\nis different: **do you trust it to run commands on your machine, unattended?**\n\nOne `rm -rf`\n\nin the wrong directory, one `git push --force`\n\nto the wrong branch, and \"the agent\n\nsaved me an afternoon\" becomes \"the agent cost me a week.\" So most people keep the agent on a\n\ntight leash — approving every step — which throws away most of the value.\n\nThere's a better middle ground. You can give an agent real autonomy on a workflow and still make\n\nit structurally unable to do the few things that would hurt. Here's the three-layer approach I\n\nuse with Claude Code.\n\nStart by telling the agent what it's allowed to touch, in `.claude/settings.json`\n\n:\n\n```\n{\n  \"permissions\": {\n    \"allow\": [\"Bash(ls:*)\", \"Bash(git status:*)\", \"Bash(git diff:*)\", \"Read(*)\", \"Grep(*)\"],\n    \"deny\": [\"Bash(sudo:*)\", \"Read(.env)\", \"Read(**/secrets/**)\"]\n  }\n}\n```\n\nThis is necessary but not sufficient. Permission globs are coarse — `Bash(git:*)`\n\nallows both\n\n`git status`\n\nand `git push --force`\n\n. For the commands that actually matter, you want logic, not\n\na glob. That's the next layer.\n\nClaude Code can run a **hook** before every tool call. If the hook exits with code `2`\n\n, the call\n\nis denied and the reason is handed back to the model. That's the perfect place to put a\n\ndeny-list of things that should never run unattended:\n\n``` python\nimport json, re, sys\n\nDANGEROUS = [\n    # rm -rf in any flag order, plus the PowerShell alias rm -Recurse -Force\n    (r\"\\brm\\b(?=.*(?:-[a-z]*r|--recursive))(?=.*(?:-[a-z]*f|--force))\", \"recursive force delete\"),\n    (r\"\\bremove-item\\b(?=.*-rec)(?=.*-for)\", \"recursive force delete (Windows)\"),\n    (r\"\\bgit\\s+push\\b(?=.*(?:--force\\b|\\s-f\\b))\", \"force push can overwrite history\"),\n    (r\"\\bgit\\s+reset\\s+--hard\\b\", \"hard reset discards work\"),\n    (r\"\\b(?:curl|wget)\\b.*\\|\\s*(?:sudo\\s+)?(?:sh|bash)\\b\", \"pipe-to-shell from the network\"),\n    # ... fork bombs, raw disk writes, sudo, format, shred, del /s, rmdir /s\n]\n\ndef main():\n    event = json.load(sys.stdin)\n    if event.get(\"tool_name\") != \"Bash\":\n        return 0\n    command = event.get(\"tool_input\", {}).get(\"command\", \"\")\n    for pattern, reason in DANGEROUS:\n        if re.search(pattern, command, re.IGNORECASE):\n            print(f\"guard: blocked -- {reason}\", file=sys.stderr)\n            return 2   # Claude Code treats exit 2 as \"deny\"\n    return 0\n\nsys.exit(main())\n```\n\nTwo details that matter more than they look:\n\n`rm -rf`\n\n. If your team runs\nClaude Code on Windows, you also need `Remove-Item -Recurse -Force`\n\n, `del /s`\n\n, `rmdir /s`\n\n,\n`format`\n\n, and friends — otherwise the guardrail is theater on half your machines.`rm -rf`\n\n, `rm -R -f`\n\n,\n`rm --recursive --force`\n\nand `rm -Rf`\n\nare the same danger. The two-lookahead regex above\ncatches \"has a recursive flag AND has a force flag\" regardless of arrangement, while still\nletting a plain `rm file.txt`\n\nthrough.Wire it up in `settings.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      { \"matcher\": \"Bash\",\n        \"hooks\": [ { \"type\": \"command\", \"command\": \"python .claude/hooks/guard.py\" } ] }\n    ]\n  }\n}\n```\n\nNow test it — this is the part people skip:\n\n```\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"rm -rf /\"}}' | python .claude/hooks/guard.py\n# -> exit 2, blocked\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | python .claude/hooks/guard.py\n# -> exit 0, allowed\n```\n\nA deny-list is friendly for interactive work on your own machine: block the known-dangerous,\n\nallow everything else. For **unattended runs or client work**, flip it to **fail-closed**: only\n\ncommands you explicitly allow run, everything else is refused. Log every decision either way, so\n\nyou have an audit trail of what the agent tried.\n\nShell isn't the only way an agent acts on your world. The moment you give it an MCP server that\n\ntalks to your API or database, the same discipline applies. The rule that keeps you safe:\n\n**Reads are free. Writes are gated.**\n\nGive the agent all the read access it needs, but make every state change require a second\n\ndeliberate signal — or refuse it outright. A read-only SQLite server, for example:\n\n``` php\n@mcp.tool()\ndef query(sql: str) -> str:\n    \"\"\"Run a SELECT query. Non-SELECT statements are refused.\"\"\"\n    if not sql.lstrip().lower().startswith(\"select\"):\n        return \"refused: only SELECT statements are allowed (read-only guardrail).\"\n    # ... open the DB with a read-only connection and run it\n```\n\nAnd for anything that mutates state, require an explicit `confirm=true`\n\nargument so the agent\n\ncan't change things by accident:\n\n``` php\n@mcp.tool()\ndef update_resource(path: str, body: str, confirm: bool = False) -> str:\n    if not confirm:\n        return \"refused: pass confirm=true to perform this write (guardrail).\"\n    # ... perform the write\n```\n\nKeep credentials in environment variables, never in code or tool arguments — that way they\n\ncan't leak into a transcript or a log.\n\nThree layers, each doing one job:\n\nWith those in place, you can point Claude Code at a real workflow — write, test, deploy, verify —\n\nand let it run, because the handful of actions that could actually hurt are structurally blocked.\n\nEverything above is in a small MIT repo you can clone and run in a minute:\n\n** https://github.com/hbouyap/claude-code-safe-automation** — a working guardrail, scoped\n\nIf you'd rather skip the assembly, I also package a fuller **MCP & Guardrails Kit** (fail-closed\n\nallow-list mode, three MCP server templates, a subagent library, and a one-command installer) —\n\nand I build these setups for teams directly. Links are on my profile.\n\n*What's your approach to giving agents autonomy safely? I'd like to hear it.*", "url": "https://wpnews.pro/news/give-claude-code-real-autonomy-without-letting-it-wreck-your-system", "canonical_source": "https://dev.to/hbouyap/give-claude-code-real-autonomy-without-letting-it-wreck-your-system-1f3e", "published_at": "2026-09-03 03:31:29+00:00", "updated_at": "2026-09-03 04:24:26.417343+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-safety"], "entities": ["Harry Philippe Mbouyap", "Claude Code", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/give-claude-code-real-autonomy-without-letting-it-wreck-your-system", "markdown": "https://wpnews.pro/news/give-claude-code-real-autonomy-without-letting-it-wreck-your-system.md", "text": "https://wpnews.pro/news/give-claude-code-real-autonomy-without-letting-it-wreck-your-system.txt", "jsonld": "https://wpnews.pro/news/give-claude-code-real-autonomy-without-letting-it-wreck-your-system.jsonld"}}