ตรวจสอบโค้ดอัตโนมัติด้วย Hooks: รับประกันคุณภาพทันทีที่เขียน

PostToolUse Hooks รัน lint อัตโนมัติหลัง Claude เขียนไฟล์ exit code 2 ส่ง error กลับให้ Claude แก้ไขอัตโนมัติ


Claude Code เขียนโค้ดได้รวดเร็ว แต่ไม่ได้จำเสมอไปว่าต้องรัน lint คุณขอให้มัน "ตรวจสอบระหว่างเขียนด้วย" บางครั้งก็ทำ บางครั้งก็ข้ามไป Hooks แก้ปัญหานี้ด้วยการย้ายความรับผิดชอบการตรวจสอบโค้ดจาก Claude ไปยังระบบ

บทความนี้โฟกัสที่สิ่งเดียว: ใช้ PostToolUse Hooks เพื่อเรียกใช้การตรวจสอบโดยอัตโนมัติหลังจาก Claude เขียนโค้ด เพื่อให้ทุกการเปลี่ยนแปลงไฟล์ตรงตามมาตรฐานของคุณ


แนวทาง: PostToolUse + กรองตามประเภทไฟล์

ลอจิกของ Hook ตรวจสอบโค้ดนั้นง่าย:

  1. Claude เรียก Write หรือ Edit เพื่อเขียนไฟล์
  2. Hook ดักจับเหตุการณ์นี้และรับ path ของไฟล์
  3. ตามนามสกุล ตัดสินใจว่าจะรันเครื่องมือตรวจสอบใด
  4. ตรวจสอบผ่าน: exit code 0 Claude ดำเนินต่อ
  5. ตรวจสอบล้มเหลว: exit code 2 ส่ง error กลับไปให้ Claude แก้ไข

exit code 2 คือกุญแจสำคัญ — ทำให้ Claude รับ error และแก้ไขอัตโนมัติ สร้างวงจร "เขียน→ตรวจสอบ→แก้ไข"


การตั้งค่าพื้นฐาน: โปรเจ็กต์ภาษาเดียว

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'"
          }
        ]
      }
    ]
  }
}

การตั้งค่าขั้นสูง: โปรเจ็กต์หลายภาษา

สร้าง .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

อ้างอิง script ใน settings.json:

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

ให้ Claude แก้ไขอัตโนมัติ

exit code 2 + stderr คือกุญแจการแก้ไขอัตโนมัติของ 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
}

ยกเว้นไฟล์ที่ไม่ต้องตรวจสอบ

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

ผลลัพธ์จริง

หลังตั้งค่า workflow จะเป็น:

  1. คุณขอให้ Claude เขียนไฟล์ Ruby
  2. Claude เขียนด้วย tool Write
  3. Hook รัน rubocop -A อัตโนมัติ — พบ 3 ปัญหา
  4. RuboCop แก้ไข 2 อัตโนมัติ อันที่ 3 ต้องการการตัดสินใจของมนุษย์
  5. ปัญหาที่ 3 ส่งกลับไปยัง Claude ผ่าน stderr
  6. Claude แก้ไขโค้ด Hook ถูกเรียกอีกครั้ง
  7. การตรวจสอบรอบสองผ่าน — เสร็จสิ้น

คุณไม่ต้องรันคำสั่งตรวจสอบด้วยตนเองเลย


Global vs. ระดับโปรเจ็กต์

Global (~/.claude/settings.json): เหมาะเมื่อใช้กฎเดียวกันในทุกโปรเจ็กต์

ระดับโปรเจ็กต์ (.claude/settings.json commit ลง git): แนะนำสำหรับทีม ใครก็ตามที่เปิด repo จะได้รับการตั้งค่าที่ถูกต้องโดยอัตโนมัติ

ทั้งสองอยู่ร่วมกันได้ hooks ระดับโปรเจ็กต์จะรวมกับ global และทำงานทั้งคู่


สรุป

การตรวจสอบโค้ดอัตโนมัติด้วย Hooks สรุปได้เป็น 3 ขั้นตอน:

  1. PostToolUse + matcher "Write|Edit" เพื่อดักจับการเขียนไฟล์
  2. รับ path ของไฟล์และส่งไปยัง linter ที่เหมาะสมตามนามสกุล
  3. เมื่อล้มเหลว ออกด้วย exit code 2 เพื่อให้ Claude เห็น error และแก้ไข

เริ่มจากภาษาเดียว เมื่อทำงานได้แล้วค่อยเพิ่มภาษาอื่น commit script ลง .claude/hooks/ เพื่อแชร์กับทีม