Free

จัดการ Git Workflow ด้วย Hooks: ให้ Claude คอมมิตโค้ดตามกฎด้วย

ใช้ PreToolUse Hooks ดักจับคำสั่ง git ใน Bash — บล็อก commit โดยตรงไปที่ main, รูปแบบ message ที่ผิด และการดำเนินการที่เป็นอันตราย


Claude Code มีสิทธิ์รัน git commit, git push และ git checkout โดยตรง ส่วนใหญ่แล้วสะดวกดี แต่ก็หมายความว่า Claude อาจจะ:

  • คอมมิตตรงไปที่ main
  • เขียน commit message ในรูปแบบที่ต้องการ
  • ลืมเพิ่มการระบุผู้ร่วมเขียน
  • ทำ force push ในเวลาที่ไม่ควร

Hooks ช่วยให้เราดักจับการดำเนินการเหล่านี้ก่อนที่จะเกิดขึ้น — ตรวจสอบ บังคับใช้มาตรฐาน หรือบล็อกการกระทำที่เป็นอันตรายโดยสิ้นเชิง


แนวทาง: PreToolUse เพื่อดักจับคำสั่ง git ใน Bash

การดำเนินการ git ทั้งหมดใน Claude Code ผ่านเครื่องมือ Bash ดังนั้นจุดดักจับคือ:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [...]
      }
    ]
  }
}

Hook รับ string คำสั่งแบบเต็มผ่าน stdin ตรวจสอบว่ามีการดำเนินการ git เฉพาะหรือไม่ แล้วตัดสินใจว่าจะอนุญาตหรือบล็อก

รูปแบบพื้นฐาน:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

if echo "$cmd" | grep -qE '^git commit'; then
  echo "ไม่เป็นไปตามมาตรฐาน" >&2
  exit 2  # บล็อกและแจ้ง Claude
fi

กฎที่ 1: ห้ามคอมมิตตรงไปที่ main

ความต้องการที่พบบ่อยที่สุด Claude ควรทำงานบน feature branch ไม่ใช่ push โดยตรงไปที่ main

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git (commit|push)' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)

if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
  echo "ไม่อนุญาตให้คอมมิตตรงไปที่ $current_branch โปรดสลับไปยัง feature branch ก่อน" >&2
  exit 2
fi

เมื่อ Claude ได้รับข้อความนี้ จะสลับไปยัง branch ที่เหมาะสมเองก่อนดำเนินการต่อ


กฎที่ 2: บังคับรูปแบบ commit message

ทีมส่วนใหญ่ใช้ Conventional Commits: feat: ..., fix: ..., docs: ... แต่ commit message ของ Claude ไม่จำเป็นต้องตรงกับรูปแบบของคุณ

ตรวจสอบก่อนคอมมิต:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0

# ดึง message หลัง -m
msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27].*?["\x27]' | tr -d '"'"'" || true)

if [[ -z "$msg" ]]; then
  exit 0  # ไม่มี flag -m น่าจะใช้ editor — ข้ามไป
fi

if ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
  echo "รูปแบบ commit message ไม่ถูกต้อง" >&2
  echo "รูปแบบที่ต้องการ: <type>(<scope>): <description>" >&2
  echo "ตัวอย่าง: feat(auth): add OAuth login" >&2
  echo "ประเภทที่ใช้ได้: feat | fix | docs | style | refactor | test | chore | perf | ci | build | revert" >&2
  exit 2
fi

Claude จะได้รับ feedback และเขียน commit message ใหม่ในรูปแบบที่ถูกต้อง


กฎที่ 3: บังคับการระบุผู้ร่วมเขียน

คุณต้องการให้ทุก commit ของ Claude มีการระบุผู้เขียน — เพื่อติดตามว่า commit ไหนมี AI เข้าร่วม แต่ไม่อยากพิมพ์เองทุกครั้ง

PostToolUse เหมาะกว่าที่นี่ — ตรวจสอบหลัง commit เสร็จ แล้วแจ้ง Claude ให้ทำ --amend ถ้าขาดการระบุ:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -qE '^git commit' || exit 0
echo "$cmd" | grep -q 'amend' && exit 0  # หลีกเลี่ยง loop ไม่สิ้นสุด

last_msg=$(git log -1 --format="%B" 2>/dev/null)
if ! echo "$last_msg" | grep -q 'Co-Authored-By'; then
  echo "Commit ขาดการระบุผู้ร่วมเขียน Co-Authored-By" >&2
  echo "รัน: git commit --amend -m \"\$(git log -1 --format='%s')\" --trailer 'Co-Authored-By: Claude Sonnet 4.6 <[email protected]>'" >&2
  exit 2
fi

กฎที่ 4: บล็อกการดำเนินการ git ที่เป็นอันตราย

บางคำสั่งยากที่จะกู้คืน:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

# บล็อก force push ไปยัง main branch
if echo "$cmd" | grep -qE 'git push.*--force(-with-lease)?.*\b(main|master)\b'; then
  echo "ไม่อนุญาตให้ force push ไปที่ main/master" >&2
  exit 2
fi

# บล็อก reset --hard (ต้องการการยืนยันอย่างชัดเจน)
if echo "$cmd" | grep -qE 'git reset --hard'; then
  echo "git reset --hard เป็นการดำเนินการที่ทำลายข้อมูล — ถูกบล็อกแล้ว หากจำเป็นจริงๆ ให้เพิ่มคอมเมนต์ # ALLOW: พร้อมระบุเหตุผล" >&2
  exit 2
fi

# บล็อกการลบ remote branch
if echo "$cmd" | grep -qE 'git push.*--delete|git push.*:'; then
  echo "ไม่อนุญาตให้ลบ remote branch ทำด้วยตนเองบน GitHub ถ้าจำเป็น" >&2
  exit 2
fi

เทคนิค # ALLOW: สำหรับ reset --hard ช่วยให้ Claude "ปลดล็อก" การดำเนินการเมื่อจำเป็นจริงๆ — พร้อมกับต้องระบุเหตุผลอย่างชัดเจน


กฎที่ 5: บันทึก audit log ของ git

บันทึกการดำเนินการ git ทั้งหมดที่ Claude รัน:

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')

echo "$cmd" | grep -q '^git' || exit 0

echo "$(date '+%Y-%m-%d %H:%M:%S') GIT: $cmd" >> ~/.claude/git-audit.log

ใช้ PostToolUse — บันทึกเฉพาะคำสั่งที่รันจริง


การตั้งค่าแบบสมบูรณ์: Hook รวมสำหรับ git workflow

.claude/hooks/git-guard.sh:

#!/bin/bash
set -e

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

[[ -z "$cmd" ]] && exit 0
echo "$cmd" | grep -q '^git' || exit 0

current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")

# 1. บล็อก commit และ push บน main/master
if echo "$cmd" | grep -qE '^git (commit|push)'; then
  if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
    echo "Branch ปัจจุบันคือ $current_branch — commit และ push โดยตรงถูกบล็อก โปรดสลับไปยัง feature branch" >&2
    exit 2
  fi
fi

# 2. ตรวจสอบรูปแบบ commit message
if echo "$cmd" | grep -qE '^git commit.*-m'; then
  msg=$(echo "$cmd" | grep -oP '(?<=-m )["\x27][^\x27"]*["\x27]' | tr -d '"'"'" || true)
  if [[ -n "$msg" ]] && ! echo "$msg" | grep -qE '^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{3,}'; then
    echo "รูปแบบ commit message ไม่ถูกต้อง" >&2
    echo "รูปแบบ: <type>(<scope>): <description>" >&2
    echo "ตัวอย่าง: fix(api): handle null response from upstream" >&2
    exit 2
  fi
fi

# 3. บล็อกการดำเนินการที่เป็นอันตราย (ยกเว้นมี override ของ ALLOW)
if echo "$cmd" | grep -qE 'git push.*--force|git reset --hard|git push.*--delete'; then
  if ! echo "$cmd" | grep -q '# ALLOW:'; then
    echo "การดำเนินการที่เป็นอันตรายถูกบล็อก: $cmd" >&2
    echo "หากต้องการดำเนินการต่อ ให้เพิ่ม # ALLOW: <เหตุผล> ต่อท้ายคำสั่ง" >&2
    exit 2
  fi
fi

settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/git-guard.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); echo \"$cmd\" | grep -q \"^git\" && echo \"$(date +\\'%Y-%m-%d %H:%M:%S\\') $cmd\" >> ~/.claude/git-audit.log || true'"
          }
        ]
      }
    ]
  }
}

สิ่งที่ควรรู้

ใช้ Bash เป็น matcher ไม่ใช่ Git

Claude Code ไม่มีเครื่องมือ Git โดยเฉพาะ — คำสั่ง git ทั้งหมดผ่าน Bash matcher ต้องเป็น "Bash"

อย่าพึ่งพา regex มากเกินไป

คำสั่ง git มีหลายรูปแบบ: git commit -m "msg", git commit --message="msg", GIT_AUTHOR_NAME=xxx git commit regex ที่เข้มงวดเกินไปจะพลาด edge case ดักจับเฉพาะรูปแบบที่อันตรายและพบบ่อยที่สุด แล้วเชื่อใจวิจารณญาณของ Claude ในส่วนที่เหลือ

การตั้งค่าแบบ global กับ project-level

การป้องกัน main branch และการตรวจสอบรูปแบบ commit ควรอยู่ในการตั้งค่าระดับ project (.claude/settings.json) เพราะชื่อ branch และ commit convention แตกต่างกันในแต่ละ project Audit log ควรอยู่ในการตั้งค่า global โดยเขียนไปยังไฟล์ ~/.claude/git-audit.log รวมกัน


สรุป

การจัดการ git workflow ด้วย Hooks ขึ้นอยู่กับ: PreToolUse + Bash matcher + การตรวจจับคำสั่ง git

สามกฎที่ควรตั้งค่าก่อน:
1. ห้าม commit โดยตรงไปที่ main
2. บังคับรูปแบบ commit message
3. ดักจับ force push, reset --hard และการดำเนินการที่ทำลายข้อมูลอื่นๆ

รวมกับ audit log ทำให้ทุกการกระทำ git ของ Claude ตรวจสอบได้ เริ่มต้นจากกฎที่ง่ายที่สุด ทดสอบให้ผ่านก่อน แล้วค่อยเพิ่มกฎต่อไป