Skill 寫法圖鑑

block-no-verify-hook

wshobson | 安全護欄 | 原文 | MIT

用 PreToolUse hook 擋下 AI agent 用 --no-verify 跳過 git pre-commit hook

中文翻譯

---
name: block-no-verify-hook
description: 設定 PreToolUse hook,防止 AI agent 用 --no-verify 和其他繞過旗標跳過 git 的 pre-commit hook。在設定要強制執行 commit 品質關卡的 Claude Code 專案時使用。
---

# Block No-Verify Hook

PreToolUse hook 設定,會在執行前攔截並擋下繞過旗標的使用,確保 AI agent 無法跳過 pre-commit hook、GPG 簽署,或其他 git 的安全機制。

## 概覽

AI 寫程式的 agent(Claude Code、Codex 等)可以執行帶有 `--no-verify` 這類旗標的 shell 指令,繞過 pre-commit hook。這會讓 pre-commit hook 裡設定的 lint、格式化、測試和安全檢查全部失去意義。block-no-verify hook 加上一道 PreToolUse 防護,在執行之前拒絕任何包含繞過旗標的 tool call。

## 問題

AI agent 在 commit 程式碼時,可能為了避開 hook 失敗而使用繞過旗標:

```bash
# These commands skip pre-commit hooks entirely
git commit --no-verify -m "quick fix"
git push --no-verify
git commit --no-gpg-sign -m "unsigned commit"
git merge --no-verify feature-branch
```

這會導致:
- 沒格式化的程式碼進到 repo
- lint 錯誤繞過檢查
- 安全掃描被跳過
- 未簽署的 commit 繞過簽署政策
- 測試套件被規避

## 解法

在 `.claude/settings.json` 加一個 `PreToolUse` hook,檢查每一個 Bash tool call,並擋下包含繞過旗標的指令。

### 設定

把下面這段加到專案的 `.claude/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

### 運作方式

1. **Matcher**:這個 hook 只針對 `Bash` 的 tool call,所以不會干擾其他工具(Read、Edit、Grep 等)。
2. **檢查**:Claude Code 會把 tool call 以 JSON 的形式從 stdin 送給 hook,而且不會設定 `$TOOL_INPUT` 變數。hook 用 `grep -E` 在那段 JSON 裡搜尋 `command` 的值,所以不需要 `jq` 或 `node`,而且其他欄位裡的文字(例如 `cwd` 或 tool call 的描述)不會觸發它。它會擋下 `--no-verify`、`--no-gpg-sign`,以及 git 接受的任何更短前綴,例如 `--no-veri`。它也會擋下同一個指令裡接在 `commit` 之後、含有 `n` 的短選項組合,例如 `-n` 或 `-nm`,因為 `-n` 是 `--no-verify` 的簡寫。hook 不會去找 `git` 這個字,所以也能抓到 `if git ...`、`sudo git ...` 和 `g=git; $g commit --no-verify`。誤判,例如 commit 訊息裡剛好提到某個旗標,也會擋下呼叫,這是比較安全的失敗方式。
3. **擋下**:如果在 git 指令裡找到繞過旗標,hook 會以 exit code 2 結束並印出錯誤訊息。exit code 2 會通知 Claude Code 完全拒絕這個 tool call。
4. **放行**:如果沒有找到繞過旗標,hook 會以 exit code 0 結束,指令正常執行。
5. **限制**:hook 檢查的是文字,所以它擋得住出於習慣去用繞過旗標的 agent,擋不住刻意想要規避它的 agent,例如把旗標拆成片段再組合,或是執行 `git -c core.hooksPath=/dev/null commit`。

### Exit Code

| Code | 意義 |
|------|---------|
| 0 | 允許 tool call 繼續 |
| 1 | 錯誤(tool call 仍然繼續,並顯示警告) |
| 2 | 完全擋下 tool call |

## 被擋下的旗標

| 旗標 | 用途 | 為什麼擋 |
|------|---------|-------------|
| `--no-verify` | 跳過 pre-commit 和 commit-msg hook | 繞過 lint、格式化、測試、安全檢查 |
| `--no-gpg-sign` | 跳過 GPG 的 commit 簽署 | 繞過 commit 簽署政策 |

## 安裝

### 每個專案各自設定

在專案根目錄建立或更新 `.claude/settings.json`:

```bash
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
EOF
```

### 全域設定

要對所有專案強制執行,就加到 `~/.claude/settings.json`:

```bash
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
EOF
```

## 驗證

測試 hook 有沒有擋下繞過旗標:

```bash
# This should be blocked by the hook:
git commit --no-verify -m "test"

# This should succeed normally:
git commit -m "test"
```

## 擴充 Hook

### 加入更多要擋的旗標

要擋更多旗標(例如 `--force`),就擴充 grep 的 pattern:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n|git([[:space:]]|\\\\t)([^\"\\\\]|\\\\.)*--force)'; then echo 'BLOCKED: Bypass flags are not allowed.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

### 與其他 Hook 合用

block-no-verify hook 可以跟其他 PreToolUse hook 並用:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: Bypass flags not allowed.' >&2; exit 2; fi"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE 'rm[[:space:]]+-rf[[:space:]]+/'; then echo 'BLOCKED: Dangerous rm command.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

## 最佳實務

1. **把設定檔 commit 進去** -- 把 `.claude/settings.json` 加進版本控制,讓所有團隊成員都受益於這個 hook。
2. **寫進新人導覽** -- 在專案的貢獻指南裡提到這個 hook,讓開發者了解為什麼繞過旗標會被擋。
3. **搭配 pre-commit hook** -- block-no-verify hook 確保 pre-commit hook 會執行;要確定你有設定有意義的 pre-commit hook。
4. **設定完要測試** -- 故意在測試 commit 裡觸發它,確認 hook 有效。

英文原文

---
name: block-no-verify-hook
description: Configure a PreToolUse hook to prevent AI agents from skipping git pre-commit hooks with --no-verify and other bypass flags. Use when setting up Claude Code projects that enforce commit quality gates.
---

# Block No-Verify Hook

PreToolUse hook configuration that intercepts and blocks bypass-flag usage before execution, ensuring AI agents cannot skip pre-commit hooks, GPG signing, or other git safety mechanisms.

## Overview

AI coding agents (Claude Code, Codex, etc.) can run shell commands with flags like `--no-verify` that bypass pre-commit hooks. This defeats the purpose of linting, formatting, testing, and security checks configured in pre-commit hooks. The block-no-verify hook adds a PreToolUse guard that rejects any tool call containing bypass flags before execution.

## Problem

When AI agents commit code, they may use bypass flags to avoid hook failures:

```bash
# These commands skip pre-commit hooks entirely
git commit --no-verify -m "quick fix"
git push --no-verify
git commit --no-gpg-sign -m "unsigned commit"
git merge --no-verify feature-branch
```

This allows:
- Unformatted code to enter the repository
- Linting errors to bypass checks
- Security scanning to be skipped
- Unsigned commits to bypass signing policies
- Test suites to be circumvented

## Solution

Add a `PreToolUse` hook to `.claude/settings.json` that inspects every Bash tool call and blocks commands containing bypass flags.

### Configuration

Add the following to your project's `.claude/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

### How It Works

1. **Matcher**: The hook targets only `Bash` tool calls, so it does not interfere with other tools (Read, Edit, Grep, etc.).
2. **Inspection**: Claude Code sends the tool call to the hook as JSON on stdin and sets no `$TOOL_INPUT` variable. The hook searches the `command` value in that JSON with `grep -E`, so it needs no `jq` or `node`, and text in other fields, such as `cwd` or the tool call's description, can't trigger it. It blocks `--no-verify`, `--no-gpg-sign`, and any shorter prefix of them that git accepts, e.g., `--no-veri`. It also blocks a short option group with `n` that follows `commit` in the same command, e.g., `-n` or `-nm`, because `-n` is the short form of `--no-verify`. The hook doesn't look for the word `git`, so it also catches `if git ...`, `sudo git ...`, and `g=git; $g commit --no-verify`. A false match, such as a commit message that mentions a flag, blocks the call, which is the safe way to fail.
3. **Blocking**: If a bypass flag is found in a git command, the hook exits with code 2 and prints an error message. Exit code 2 signals Claude Code to reject the tool call entirely.
4. **Pass-through**: If no bypass flag is found, the hook exits with code 0 and the command executes normally.
5. **Limits**: The hook checks text, so it stops an agent that reaches for a bypass flag out of habit. It doesn't stop an agent that sets out to evade it, e.g., by building the flag from pieces or by running `git -c core.hooksPath=/dev/null commit`.

### Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Allow the tool call to proceed |
| 1 | Error (tool call still proceeds, warning shown) |
| 2 | Block the tool call entirely |

## Blocked Flags

| Flag | Purpose | Why Blocked |
|------|---------|-------------|
| `--no-verify` | Skips pre-commit and commit-msg hooks | Bypasses linting, formatting, testing, security checks |
| `--no-gpg-sign` | Skips GPG commit signing | Bypasses commit signing policy |

## Installation

### Per-Project Setup

Create or update `.claude/settings.json` in your project root:

```bash
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
EOF
```

### Global Setup

To enforce across all projects, add to `~/.claude/settings.json`:

```bash
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'EOF'
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: --no-verify and --no-gpg-sign flags are not allowed. Run the commit without bypass flags so that pre-commit hooks execute properly.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
EOF
```

## Verification

Test that the hook blocks bypass flags:

```bash
# This should be blocked by the hook:
git commit --no-verify -m "test"

# This should succeed normally:
git commit -m "test"
```

## Extending the Hook

### Adding More Blocked Flags

To block additional flags (e.g., `--force`), extend the grep pattern:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n|git([[:space:]]|\\\\t)([^\"\\\\]|\\\\.)*--force)'; then echo 'BLOCKED: Bypass flags are not allowed.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

### Combining with Other Hooks

The block-no-verify hook works alongside other PreToolUse hooks:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE '\"command\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*(--no-(ver|g)|commit([^\"\\\\]|\\\\.)*([[:space:]]|\\\\[tn])-[a-zA-Z]*n)'; then echo 'BLOCKED: Bypass flags not allowed.' >&2; exit 2; fi"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "if grep -qE 'rm[[:space:]]+-rf[[:space:]]+/'; then echo 'BLOCKED: Dangerous rm command.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
```

## Best Practices

1. **Commit the settings file** -- Add `.claude/settings.json` to version control so all team members benefit from the hook.
2. **Document in onboarding** -- Mention the hook in your project's contributing guide so developers understand why bypass flags are blocked.
3. **Pair with pre-commit hooks** -- The block-no-verify hook ensures pre-commit hooks run; make sure you have meaningful pre-commit hooks configured.
4. **Test after setup** -- Verify the hook works by intentionally triggering it in a test commit.