hooks-permission-request-hook

v2026.09.24

Generate a PermissionRequest hook with auto-approve/deny rules. Use when needing a safer alternative to --dangerouslySkipPermissions tailored to your project stack.

GitHub
Install command
npx skhub add laurigates/hooks-permission-request-hook
Markdown
SKILL.md

/hooks:permission-request-hook

Generate a PermissionRequest hook that auto-approves safe operations, auto-denies dangerous ones, and passes everything else through for user decision. A safer, project-aware alternative to --dangerouslySkipPermissions.

When to Use This Skill

Use this skill when...Use /hooks:hooks-configuration instead when...
You want auto-approve/deny rules for Claude Code permissionsConfiguring other hook types (PreToolUse, Stop, SessionStart)
Replacing --dangerouslySkipPermissions with targeted rulesNeed general hooks knowledge or debugging
Setting up project-specific permission automationWriting entirely custom hook logic from scratch
You need a test harness to validate approve/deny behaviorUnderstanding hook lifecycle events

Auto Mode vs PermissionRequest Hook

Auto mode (Claude Code 2.1.83+) routes most approve/deny decisions through a classifier model. It overlaps with — but does not replace — a PermissionRequest hook. Choose this skill when you need any of:

  • Deterministic, auditable rules — the classifier's probabilistic answer is unsuitable for compliance contexts
  • Project-specific deny lists that go beyond the default trust set
  • Hard 0% false-positive guarantees for specific commands (the hook is exact, the classifier is not)
  • Pre-approve narrow patterns so they skip the classifier round-trip and avoid latency/token cost

Hooks coexist with auto mode — they fire alongside the classifier. See .claude/rules/auto-mode.md for the full auto-mode model and how it interacts with allow rules and subagents.

Context

Detect project stack:

  • Lockfiles: !find . -maxdepth 1 \( -name 'package-lock.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' -o -name 'bun.lockb' -o -name 'poetry.lock' -o -name 'uv.lock' -o -name 'Cargo.lock' -o -name 'go.sum' -o -name 'Gemfile.lock' \)
  • Project files: !find . -maxdepth 1 \( -name 'package.json' -o -name 'pyproject.toml' -o -name 'requirements.txt' -o -name 'Cargo.toml' -o -name 'go.mod' -o -name 'Gemfile' \)
  • Existing settings: !find . -path '*/.claude/settings.json' -maxdepth 3 -type f
  • Existing hooks dir: !find . -maxdepth 2 -type d -name 'scripts'
  • jq available: !jq --version
  • Existing PermissionRequest hooks: !find . -path '*/.claude/settings.json' -maxdepth 3 -exec jq -r '.hooks.PermissionRequest // empty' {} +

Parameters

Parse these from $ARGUMENTS:

FlagDefaultDescription
--strictoffDeny unrecognized Bash commands by default instead of passing through to user
--category <name>allInclude only specific rule categories. Repeatable. Values: git, test, lint, build, gh, deny

Execution

Execute this workflow:

Step 1: Detect project stack

Identify languages and tooling from the context above.

Language detection:

File PresentLanguagePackage Manager (from lockfile)
package.jsonNode.jsnpm (package-lock.json), yarn (yarn.lock), pnpm (pnpm-lock.yaml), bun (bun.lockb)
pyproject.toml / requirements.txtPythonpoetry (poetry.lock), uv (uv.lock), pip (fallback)
Cargo.tomlRustcargo
go.modGogo modules
GemfileRubybundler

Report detected stack to user before generating.

Step 2: Generate the hook script

Create the script at scripts/permission-request.sh (or .claude/hooks/permission-request.sh if no scripts/ directory exists).

Use the Script Template from REFERENCE.md. Adapt it by:

  1. Including only sections for detected languages (remove {{ if ... }} markers)
  2. Including only selected categories if --category flags were provided
  3. Removing all template comments ({{ ... }})
  4. If --strict is set, include the strict mode catch-all deny at the end

Step 3: Generate the test script

Create scripts/test-permission-hook.sh (or .claude/hooks/test-permission-hook.sh to match the hook location).

Use the Test Script Specification from REFERENCE.md. Include test cases only for detected stacks and selected categories. Remove all {{ ... }} template markers.

Step 4: Configure .claude/settings.json

Read existing .claude/settings.json if it exists. Merge the PermissionRequest hook — preserve all existing configuration.

If a PermissionRequest hook already exists, ask the user whether to:

  • Replace the existing PermissionRequest hook
  • Add alongside the existing hook (both will run)
  • Abort and keep existing configuration

Configuration to merge:

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "bash \"$CLAUDE_PROJECT_DIR/scripts/permission-request.sh\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Use timeout: 10 (10 seconds). Use empty matcher "" to match all tools. Adjust path if script is in .claude/hooks/ instead of scripts/.

Step 5: Finalize

  1. Make both scripts executable: chmod +x <hook-path> <test-path>
  2. Create .claude/ directory if needed for settings.json
  3. Run the test script to verify all test cases pass
  4. Report summary:
    • List files created/modified
    • Show number of approve rules, deny rules
    • Show test results (pass/fail count)

Post-Actions

After generating the hook:

  1. Suggest committing the new files:
    scripts/permission-request.sh
    scripts/test-permission-hook.sh
    .claude/settings.json
    
  2. If --strict was NOT used, mention the flag for environments where unknown commands should be denied
  3. Explain how to add custom rules — edit the APPROVE/DENY sections of the generated script
  4. Remind about CLAUDE_HOOKS_DISABLE_PERMISSION_REQUEST=1 to toggle the hook off temporarily
  5. Note that empty matcher "" catches all tools; suggest narrowing to "Bash" if only Bash commands need filtering

PermissionRequest Schema

Input (via stdin)

FieldTypeDescription
session_idstringCurrent session ID
tool_namestringTool being invoked (Bash, Write, Edit, Read, etc.)
tool_inputobjectTool-specific input (.command for Bash, .file_path for Write/Edit)
permission_typestringAlways "tool_use"
descriptionstringHuman-readable description of the operation

Output (via stdout)

DecisionJSONEffect
Approve{"decision":"approve","reason":"..."}Tool runs without user prompt
Deny{"decision":"deny","reason":"..."}Tool blocked, reason shown to Claude
PassthroughExit 0 with no outputUser prompted as normal

Agentic Optimizations

ContextApproach
Quick setup, all categories/hooks:permission-request-hook
Strict mode (deny unknown commands)/hooks:permission-request-hook --strict
Only git and test rules/hooks:permission-request-hook --category git --category test
Only deny rules (block dangerous ops)/hooks:permission-request-hook --category deny
Test the hook manuallyecho '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | bash scripts/permission-request.sh
Disable hook temporarilyCLAUDE_HOOKS_DISABLE_PERMISSION_REQUEST=1

Quick Reference

ItemValue
Hook eventPermissionRequest
Script locationscripts/permission-request.sh or .claude/hooks/permission-request.sh
Test scriptscripts/test-permission-hook.sh
Settings location.claude/settings.json
Timeout10 seconds
Matcher"" (all tools)
ToggleCLAUDE_HOOKS_DISABLE_PERMISSION_REQUEST=1
Decisionsapprove, deny, passthrough (no output)
Categoriesgit, test, lint, build, gh, deny
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

hooks-plugin/skills/hooks-permission-request-hook

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3