{"slug": "i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in", "title": "I Built a PreToolUse Hook to Require Confirmation for Selected Commands—even in Claude Code's Auto Mode", "summary": "A developer built an open-source tool called PolicyApprovalGate, a PreToolUse hook that enforces rule-based policies before Claude Code or Codex CLI executes Bash commands, denying or requiring confirmation for dangerous operations. The tool, written in Go, checks commands against built-in rules for sensitive paths and risky operations, and records decisions in an audit log. It was created after the developer's Claude Code attempted to run a command explicitly prohibited in CLAUDE.md, highlighting the need for enforceable execution-time rules.", "body_md": "I built an open-source tool called **PolicyApprovalGate**. It uses local rules to deny commands or require confirmation immediately before Claude Code or Codex CLI runs a Bash command.\n\nPolicyApprovalGate is a PreToolUse hook that applies rule-based policies before Claude Code or Codex CLI runs a Bash command. It checks dangerous commands, pushes to protected branches, and access to out-of-project or sensitive paths, then records decisions in an audit log.\n\nImportant\n\nPolicyApprovalGate complements your existing permission model, sandbox, and human review. It is neither a complete shell analyzer nor a security boundary, and should not be your only line of defense.\n\nThe built-in rules are intended to reduce missed, clearly dangerous operations, not to block every possible risk. By default, operations that cannot be classified are delegated to the host's normal approval flow.\n\nI started this project after Claude Code tried to run a command that I had explicitly told it not to run in `CLAUDE.md`\n\n.\n\n`CLAUDE.md`\n\nis useful for communicating project policies, coding conventions, and preferred workflows. However, writing a prohibition there does not make it an enforceable rule at execution time.\n\nThe official [Claude Code documentation](https://code.claude.com/docs/en/memory) describes `CLAUDE.md`\n\nas instructions loaded into the model's context, not as a hard enforcement layer. Instructions may not always be followed exactly, especially when they are ambiguous or conflict with one another.\n\nThis was particularly concerning in Auto mode, which reduces confirmation prompts so that Claude Code can work on longer tasks with fewer interruptions.\n\nAuto mode currently uses a separate classifier to review each tool call. It also takes `CLAUDE.md`\n\ninto account, but the [documentation states that this does not guarantee safety](https://code.claude.com/docs/en/permission-modes).\n\nI therefore decided to add a separate check that runs every time, immediately before a Bash command is executed.\n\nI wanted it to do four things:\n\nThat is why I built PolicyApprovalGate.\n\nPolicyApprovalGate is a policy gate written in Go that runs as a `PreToolUse`\n\nhook for Claude Code and Codex CLI.\n\nWhen an agent attempts to run a Bash command, PolicyApprovalGate receives the tool call through standard input and checks it against the configured rules.\n\nIt returns one of the following results:\n\n| Decision | Claude Code | Codex CLI |\n|---|---|---|\n`deny` |\nRejects the command | Rejects the command |\n`ask` |\nPrompts the user for confirmation | Converts the result to `deny` because standalone `ask` is not supported |\n| No decision | Delegates to the normal approval flow | Delegates to the normal approval flow |\n\nPolicyApprovalGate never executes the command itself. It only inspects the command string and paths, then returns a decision to the host.\n\nThe built-in rules check operations such as:\n\n`/`\n\nor the current user's home directory`.env`\n\nfiles, SSH keys, credential files, and other sensitive pathsPolicyApprovalGate parses shell syntax with `mvdan.cc/sh`\n\nrather than relying solely on simple regular expressions.\n\nThis allows it to track cases such as the following where possible:\n\n`cd /tmp && rm -rf target`\n\n`env`\n\nand `command`\n\n`git -C`\n\n`cp -t`\n\nor `install --target-directory`\n\nHowever, PolicyApprovalGate is not a complete shell interpreter. Unsupported syntax and operations that cannot be determined statically are delegated to the host's normal approval flow by default.\n\nPreventing commands from modifying or deleting data is only part of the problem. It is equally important to avoid letting the agent read secrets into its context.\n\nPolicyApprovalGate's built-in configuration treats paths such as the following as `sensitive_paths`\n\n:\n\n`.env`\n\nfiles and their variants`.ssh`\n\ndirectories and SSH keys`.pem`\n\n, `.p12`\n\n, `.pfx`\n\n, and `.key`\n\nfiles`.netrc`\n\nBy default, supported Bash commands return `ask`\n\nfor reads and `deny`\n\nfor writes and deletions. Claude Code asks for confirmation before a read, while Codex CLI converts `ask`\n\nto `deny`\n\n.\n\nIf you do not want these files to be read even after confirmation, keep the existing patterns generated by `policygate init`\n\nand change `sensitive_paths.policy.read`\n\nto `deny`\n\nin `~/.policygate/config.yaml`\n\n:\n\n```\n sensitive_paths:\n   policy:\n-    read: \"ask\"\n+    read: \"deny\"\n     write: \"deny\"\n     delete: \"deny\"\n```\n\nAfter changing the setting, you can use `evaluate`\n\nto check the decision without reading the actual file:\n\n```\npolicygate check-config\npolicygate evaluate --host claude --command 'cat ~/.ssh/id_rsa'\n```\n\nThere is an important limitation: PolicyApprovalGate only evaluates Bash tool calls. It does not inspect direct reads performed through Claude Code's `Read`\n\n, `Grep`\n\n, or `Glob`\n\ntools. Nor can it prevent every read performed through unsupported commands or arbitrary child processes.\n\nTo prevent Claude Code's own file tools from reading sensitive files under your home directory, configure the standard `permissions.deny`\n\nrules as well:\n\n```\n{\n  \"permissions\": {\n    \"deny\": [\n      \"Read(~/.ssh/**)\",\n      \"Read(~/.aws/**)\",\n      \"Read(~/.gnupg/**)\",\n      \"Read(~/.config/gh/**)\",\n      \"Read(**/.env*)\"\n    ]\n  }\n}\n```\n\nThe [Claude Code permissions documentation](https://code.claude.com/docs/en/permissions#read-and-edit) explains that `Read`\n\ndeny rules apply not only to built-in file tools but also to Bash file commands that Claude Code recognizes, including `cat`\n\n, `head`\n\n, `tail`\n\n, and `sed`\n\n. They do not cover arbitrary subprocesses, such as Python or Node.js programs that access files directly. If every child process must be restricted at the OS level, combine these controls with Claude Code's sandbox filesystem restrictions, a container, or a dedicated operating-system user.\n\nIn other words, the responsibilities are divided across these layers:\n\n`permissions.deny`\n\nto block Claude Code's built-in file tools and recognized Bash file commands`sensitive_paths`\n\nto confirm or deny access by supported Bash commands, providing an additional shared layer for Claude Code and Codex CLIPolicyApprovalGate does not completely protect secrets on its own. It is an additional layer designed to work alongside existing permission controls.\n\nPolicyApprovalGate stores its configuration in YAML.\n\nFor example, you can add an `ask`\n\nrule when you always want confirmation before `git push`\n\n:\n\n```\nask:\n  - pattern: '(^|[;&|])\\s*(/\\S*/)?git\\s+push\\b'\n    reason: \"Confirm before pushing to a remote\"\n```\n\nWhen this rule matches in Claude Code, the PreToolUse hook returns `ask`\n\n.\n\nAccording to the [Claude Code Hooks documentation](https://code.claude.com/docs/en/hooks#pretooluse-decision-control), an `ask`\n\nresult from a PreToolUse hook forces a confirmation prompt even in Auto mode, so the classifier cannot silently approve the command. A `deny`\n\nresult rejects the command itself.\n\nCodex CLI does not support a standalone `ask`\n\ndecision from PreToolUse. PolicyApprovalGate therefore converts `ask`\n\nto `deny`\n\nwhen invoked with `--host codex`\n\n, choosing the safer behavior instead of allowing the command to proceed without confirmation.\n\nAt the time of writing, PolicyApprovalGate does not yet have a tagged release, so build it from the repository:\n\n```\ngit clone https://github.com/nobuo-miura/PolicyApprovalGate.git\ncd PolicyApprovalGate\ngo build -o policygate ./cmd/policygate\nsudo install -m 0755 policygate /usr/local/bin/policygate\n```\n\nCreate the configuration file:\n\n```\npolicygate init\npolicygate check-config\n```\n\nThe `evaluate`\n\ncommand lets you check the decision without executing the command being evaluated:\n\n```\npolicygate evaluate --host claude --command 'rm -rf /'\n```\n\nIt returns a `deny`\n\ndecision as JSON. For readability, the example below is formatted and omits the `matched_by`\n\nfield included in the actual output:\n\n```\n{\n  \"decision\": \"deny\",\n  \"reason\": \"Recursive force-delete of / or $HOME\",\n  \"source\": \"deny_rule\"\n}\n```\n\nInstead of enabling the hook immediately after changing the configuration, you can first use `check-config`\n\nand `evaluate`\n\nto verify that it produces the intended decisions.\n\nAdd the PreToolUse hook to `.claude/settings.json`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/local/bin/policygate --host claude\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nIf you are not ready to let the hook block commands, start in `observe`\n\nmode and review only the decisions and audit log:\n\n```\n{\n  \"type\": \"command\",\n  \"command\": \"/usr/local/bin/policygate observe --host claude\"\n}\n```\n\nAdd the hook to `~/.codex/config.toml`\n\n:\n\n```\n[[hooks.PreToolUse]]\nmatcher = \"^Bash$\"\n\n[[hooks.PreToolUse.hooks]]\ntype = \"command\"\ncommand = \"/usr/local/bin/policygate --host codex\"\n```\n\nAfter registering it, run `/hooks`\n\nin Codex, review the displayed hook, and trust it. Hooks that have not been trusted, or that have changed since they were trusted, are not executed.\n\nIf you only use Claude Code and need to reliably block a simple command or path, the standard `permissions.deny`\n\nrules should be your first choice. Managed settings are also available when an organization needs to enforce these rules.\n\nPolicyApprovalGate is intended for cases such as:\n\nYou do not have to choose one or the other. PolicyApprovalGate is intended to be layered onto the areas where it is needed while retaining the host's standard permission controls and sandbox.\n\nPolicyApprovalGate is a defense-in-depth guardrail, not a security boundary.\n\nIt is not a replacement for remote branch protection, OS permissions, sandboxing, or human review.\n\nWriting a prohibition in `CLAUDE.md`\n\nand mechanically enforcing a rule immediately before execution serve different purposes.\n\nYou can use `CLAUDE.md`\n\nto communicate everyday development policies, while a PreToolUse hook checks hard boundaries such as “never execute this command” or “always ask before doing this.” PolicyApprovalGate brings this two-layer approach to both Claude Code and Codex CLI.\n\nPolicyApprovalGate is a PreToolUse hook that applies rule-based policies before Claude Code or Codex CLI runs a Bash command. It checks dangerous commands, pushes to protected branches, and access to out-of-project or sensitive paths, then records decisions in an audit log.\n\nImportant\n\nPolicyApprovalGate complements your existing permission model, sandbox, and human review. It is neither a complete shell analyzer nor a security boundary, and should not be your only line of defense.\n\nThe built-in rules are intended to reduce missed, clearly dangerous operations, not to block every possible risk. By default, operations that cannot be classified are delegated to the host's normal approval flow.", "url": "https://wpnews.pro/news/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in", "canonical_source": "https://dev.to/miura/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in-claude-codes-auto-2bcn", "published_at": "2026-08-11 11:11:17+00:00", "updated_at": "2026-08-11 11:18:05.995708+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-safety"], "entities": ["PolicyApprovalGate", "Claude Code", "Codex CLI", "Go", "mvdan.cc/sh"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in", "markdown": "https://wpnews.pro/news/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in.md", "text": "https://wpnews.pro/news/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in.txt", "jsonld": "https://wpnews.pro/news/i-built-a-pretooluse-hook-to-require-confirmation-for-selected-commands-even-in.jsonld"}}