Skip to content

feat: add deny-with-reason plugin#33809

Open
mattwwarren wants to merge 2 commits intoanthropics:mainfrom
mattwwarren:feat/deny-with-reason-plugin
Open

feat: add deny-with-reason plugin#33809
mattwwarren wants to merge 2 commits intoanthropics:mainfrom
mattwwarren:feat/deny-with-reason-plugin

Conversation

@mattwwarren
Copy link

Summary

  • Adds a deny-with-reason plugin that denies tool calls matching configured patterns and sends the reason to Claude as a system message, so it understands why and adjusts immediately
  • Users configure rules in a simple .claude/deny-reasons.yaml file with ToolName(glob) pattern syntax
  • Supports YAML (with PyYAML) and JSON config formats, stdlib-only dependencies
  • Includes 36 pytest tests covering pattern parsing, rule matching, config loading, response format, and end-to-end subprocess invocation

Addresses #29782

How it works

The plugin registers a PreToolUse hook. When Claude attempts a tool call matching a rule:

  1. The tool call is denied via permissionDecision: "deny"
  2. The reason is sent as a systemMessage
  3. Claude sees the reason and course-corrects

Example config:

rules:
  - pattern: "Bash(pnpm *)"
    reason: "Use npm instead of pnpm — this project uses npm"
  - pattern: "Edit(*.env)"
    reason: "Don't modify .env files — use .env.example as a template"

Scope

This addresses the configuration-based half of #29782. The interactive half (typing a reason at the TUI deny prompt) would require either a core binary change or a new hook event (e.g., ToolDenied / PermissionDenied) that plugins could tap into — recommending this as a complementary core enhancement.

Test plan

uvx pytest plugins/deny-with-reason/tests/ -v
  • 36 pytest tests (34 passed, 2 skipped — YAML tests skip when PyYAML unavailable)
  • ruff check: zero violations
  • ruff format: already formatted
  • mypy: no issues

🤖 Generated with Claude Code

mattwwarren and others added 2 commits March 12, 2026 19:57
When Claude attempts a tool call that matches a configured pattern,
the plugin denies it and sends the reason as a system message so
Claude understands why and adjusts its approach immediately.

Users configure rules in .claude/deny-reasons.yaml:

  rules:
    - pattern: "Bash(pnpm *)"
      reason: "Use npm instead of pnpm"

Addresses anthropics#29782

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
36 tests covering pattern parsing, rule matching, config loading,
response format, and end-to-end subprocess invocation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant