Kiểm tra code tự động với Hooks: Chất lượng đảm bảo ngay khi viết

Dùng PostToolUse Hooks để tự động kích hoạt lint sau khi Claude viết file. Exit code 2 gửi lỗi lại cho Claude tự động sửa.


Claude Code viết code nhanh, nhưng không phải lúc nào cũng nhớ chạy lint. Bạn bảo nó "vừa viết vừa kiểm tra luôn", đôi khi làm, đôi khi bỏ qua. Hooks giải quyết vấn đề này — chuyển trách nhiệm kiểm tra code từ Claude sang hệ thống.

Bài viết này tập trung vào một điều: dùng PostToolUse Hooks để tự động kích hoạt kiểm tra sau khi Claude viết code, đảm bảo mọi thay đổi file đều đáp ứng tiêu chuẩn của bạn.


Cách tiếp cận: PostToolUse + lọc theo loại file

Logic của Hook kiểm tra code rất đơn giản:

  1. Claude gọi Write hoặc Edit để viết file
  2. Hook chặn sự kiện này và lấy đường dẫn file
  3. Dựa vào phần mở rộng, quyết định chạy công cụ kiểm tra nào
  4. Kiểm tra qua: exit code 0, Claude tiếp tục
  5. Kiểm tra thất bại: exit code 2, lỗi được phản hồi lại cho Claude để sửa

Exit code 2 là chìa khóa — khiến Claude nhận lỗi và tự động sửa, tạo vòng lặp "viết→kiểm tra→sửa".


Cấu hình cơ bản: Dự án một ngôn ngữ

Ruby (RuboCop)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
          }
        ]
      }
    ]
  }
}

JavaScript/TypeScript (ESLint)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" =~ \\.(js|ts|jsx|tsx)$ ]] && npx eslint --fix \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Python (ruff)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.py ]] && ruff check --fix \"$file\" && ruff format \"$file\" || true'"
          }
        ]
      }
    ]
  }
}

Cấu hình nâng cao: Dự án đa ngôn ngữ

Chuyển logic kiểm tra vào script độc lập để dễ bảo trì hơn.

Tạo .claude/hooks/lint.sh:

#!/bin/bash
set -e

input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // empty')

[[ -z "$file" ]] && exit 0
[[ ! -f "$file" ]] && exit 0

ext="${file##*.}"

case "$ext" in
  rb)    rubocop -A "$file" --no-color -q ;;
  js|ts|jsx|tsx) npx eslint --fix "$file" --quiet ;;
  py)    ruff check --fix "$file"; ruff format "$file" ;;
  go)    gofmt -w "$file"; golangci-lint run "$file" 2>&1 ;;
  *)     exit 0 ;;
esac

Tham chiếu script trong settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "bash .claude/hooks/lint.sh" }]
      }
    ]
  }
}

Để Claude tự động sửa

Exit code 2 + stderr là chìa khóa kích hoạt tự động sửa của Claude:

run_lint() {
  local output exit_code
  output=$(rubocop "$file" --no-color 2>&1)
  exit_code=$?
  if [[ $exit_code -ne 0 ]]; then
    echo "$output" >&2
    exit 2
  fi
  rubocop -A "$file" --no-color -q
}

Loại trừ các file không cần kiểm tra

skip_patterns=("vendor/" "node_modules/" "db/schema.rb" ".min.js" "_test.go")
for pattern in "${skip_patterns[@]}"; do
  [[ "$file" == *"$pattern"* ]] && exit 0
done

Kết quả thực tế

Sau khi cấu hình, quy trình làm việc trở thành:

  1. Bạn yêu cầu Claude viết file Ruby
  2. Claude viết bằng tool Write
  3. Hook tự động chạy rubocop -A — phát hiện 3 vấn đề
  4. RuboCop tự sửa 2 vấn đề; vấn đề thứ 3 cần phán đoán của con người
  5. Lỗi thứ 3 được phản hồi cho Claude qua stderr
  6. Claude sửa code, Hook kích hoạt lại
  7. Kiểm tra lần hai qua — hoàn tất

Bạn không cần chạy bất kỳ lệnh kiểm tra thủ công nào.


Global vs. cấp độ dự án

Global (~/.claude/settings.json): Phù hợp khi dùng cùng quy tắc cho tất cả dự án.

Cấp độ dự án (.claude/settings.json commit vào git): Khuyến nghị cho team. Ai mở repo cũng tự động có cấu hình đúng.

Hai cấp độ có thể cùng tồn tại và được hợp nhất khi chạy.


Tóm tắt

Kiểm tra code tự động với Hooks chỉ gồm ba bước:

  1. PostToolUse + matcher "Write|Edit" để chặn ghi file
  2. Lấy đường dẫn file và chuyển đến linter phù hợp theo phần mở rộng
  3. Khi thất bại, thoát với exit code 2 để Claude thấy lỗi và sửa

Bắt đầu với một ngôn ngữ. Khi hoạt động, thêm ngôn ngữ khác. Commit script vào .claude/hooks/ để chia sẻ với team.