Claude Code Hooks: 5 Recipes ใหม่สำหรับทีม Dev ไทย 2026

เนื้อหาในบทความนี้

TL;DR — สรุปก่อนยาว

Pre-commit hooks ของ Claude Code คือเรื่องของเมื่อปีที่แล้ว ปี 2026 ทีมที่ใช้ hooks แบบเอาจริงเขาขยับไปอีก layer คือ “lifecycle hooks” ที่จับ event นอกเหนือจาก PreToolUse — ทั้ง SubagentStop, PreCompact/PostCompact, PostToolUse กับ matcher Write/Edit, ConfigChange และ Elicitation. โพสต์นี้คือ 5 recipes ที่ทีม dev ไทยเอาไป copy ใส่ .claude/settings.json ได้เลย แต่ละสูตรมี JSON config + bash script ครบ พร้อมเหตุผลว่าทำไมทีมไทยถึงควรใช้ — ไม่ใช่แค่ “เพราะ Anthropic บอก” แต่เพราะมัน save เวลาและกัน accident ได้จริงในวงการเราที่ context ทีมเปลี่ยนเร็ว และโปรเจกต์มักเป็น monorepo. ใครยังไม่ได้อ่าน pre-commit recipes ของเรา แนะนำให้เริ่มที่นั่นก่อน แล้วค่อยมาต่อยอดด้วย 5 สูตรชุดใหม่นี้.


ทำไมต้องขยับจาก pre-commit ไป lifecycle hooks?

ในเดือนเมษายน 2026 Anthropic ขยาย hook events จาก 14 เป็น 25 events — InstructionsLoaded, ConfigChange, PostCompact, Elicitation/ElicitationResult และอื่นๆ. สิ่งที่ทีมไทยส่วนใหญ่พลาดคือคิดว่า hooks = pre-commit script เก๋ๆ ทั้งที่จริงๆ มัน “control plane” ของ Claude Code ตั้งแต่ session เริ่มยันจบ.

ถ้าทีมคุณเจอปัญหาเหล่านี้แม้แต่อย่างเดียว 5 recipes ด้านล่างจะช่วยได้:

  • Subagent วิ่งไปนานแล้วไม่รู้ว่าจบเมื่อไหร่ → SubagentStop hook
  • เปิด session นานๆ แล้ว context หายตอน compaction → PreCompact + PostCompact
  • Monorepo มี cache layer (Turborepo / Nx) ที่ Claude แก้ไฟล์แล้ว cache ไม่ invalidate → PostToolUse matcher Write/Edit
  • กลัวคนในทีมแอบแก้ .claude/settings.json แล้ว disable security hooks → ConfigChange (real blocking hook)
  • MCP server บางตัวเรียก elicitation ขออินพุตจาก user แล้วเรา auto-approve ไม่ดี → Elicitation hook

ทั้ง 5 ข้อนี้คือสิ่งที่ทีม dev ไทยถามผมเยอะที่สุดในช่วง 4 สัปดาห์ที่ผ่านมาที่ไปเทรน ลองอ่านบทความเรื่อง 25 hook events ของเราเพื่อปูพื้นก่อน แล้วต่อด้วย recipes ข้างล่างนี้.


Recipe 1 — SubagentStop: แจ้งทีมเมื่อ subagent ทำงานเสร็จ

ปัญหา: สั่ง Task tool ให้ subagent ไปวิ่งงานยาว เช่น run test suite 10 นาที, หรือ Explore agent หาเคสใน codebase ขนาด 200k LOC แล้วเรา switch ไปทำงานอื่น พอกลับมาก็ลืมว่าใครเสร็จยัง.

Hook: SubagentStop fires ทุกครั้งที่ subagent (Task tool) จบงาน ตาม official docs. มันรับ payload เป็น JSON ที่มี subagent_type และ session info แล้ว forward เข้า Slack / LINE / desktop notification ได้.

Code: ส่งไป Slack webhook + desktop notification (macOS/Linux)

ใส่ใน .claude/hooks/subagent-stop-notify.sh:

#!/bin/bash
# .claude/hooks/subagent-stop-notify.sh
INPUT=$(cat)
AGENT_TYPE=$(echo "$INPUT" | jq -r '.subagent_type // "unknown"')
PROJECT=$(basename "$CLAUDE_PROJECT_DIR")

# Desktop notification (cross-platform)
if [[ "$OSTYPE" == "darwin"* ]]; then
  osascript -e "display notification \"Subagent ${AGENT_TYPE} finished in ${PROJECT}\" with title \"Claude Code\""
else
  notify-send "Claude Code" "Subagent ${AGENT_TYPE} finished in ${PROJECT}"
fi

# Slack (optional - skip if no webhook)
if [ -n "$SLACK_WEBHOOK_URL" ]; then
  curl -sS -X POST "$SLACK_WEBHOOK_URL" \
    -H 'Content-Type: application/json' \
    -d "{\"text\":\":robot_face: Subagent \`${AGENT_TYPE}\` finished — project \`${PROJECT}\`\"}" \
    > /dev/null
fi

exit 0

ใน .claude/settings.json:

{
  "hooks": {
    "SubagentStop": [
      {
        "hooks": [
          { "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/subagent-stop-notify.sh",
            "background": true,
            "timeout": 10 }
        ]
      }
    ]
  }
}

ทำไมต้องใส่ "background": true? เพราะ notification ไม่ต้องการให้ Claude รอผล — Anthropic release background hooks ในเดือนมกราคม 2026 เพื่อกรณีนี้พอดี. ใส่แล้วประหยัดเวลา agent loop ได้ 1-3 วินาทีต่อ subagent.

Insight สำหรับทีมไทย: ถ้าทีมคุณใช้ LINE OA หรือ LINE Notify (RIP — แต่ webhook ไป LINE Bot ยังใช้ได้) replace Slack webhook ด้วย LINE push API ก็จบ. ตัวอย่าง LINE flex สำหรับงานสไตล์นี้เราเคยเขียนไว้ใน skill plugin connector guide.


Recipe 2 — PreCompact + PostCompact: กัน context หายตอน compaction

ปัญหา: session ยาวๆ พอ context window เต็ม Claude จะ summarize conversation ทิ้ง — และ “ของสำคัญที่ไม่ใช่ recent” หายไป เช่น path ไฟล์ที่เคยอ่าน, decision ที่ตกลงกันไว้ครึ่งทาง, sprint focus.

Hook pair: PreCompact fires ก่อน compaction (cannot block, แค่ log/backup ได้), PostCompact fires หลังเสร็จ และ — สำคัญที่สุด — output จาก SessionStart กับ matcher compact จะถูก inject กลับเข้า context เป็น additionalContext ตาม hook reference ของ Anthropic.

Code: extract decisions + re-inject after compaction

.claude/hooks/pre-compact-snapshot.sh:

#!/bin/bash
# Snapshot critical state before compaction
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
SNAPSHOT_DIR="$CLAUDE_PROJECT_DIR/.claude/snapshots"
mkdir -p "$SNAPSHOT_DIR"

# Capture: current git branch + recent file paths + manual notes
cat > "$SNAPSHOT_DIR/${SESSION_ID}.md" <<EOF
# Session ${SESSION_ID} — pre-compact snapshot
Time: $(date "+%Y-%m-%d %H:%M:%S")
Branch: $(git -C "$CLAUDE_PROJECT_DIR" branch --show-current 2>/dev/null)
Recent changes:
$(git -C "$CLAUDE_PROJECT_DIR" diff --name-only HEAD 2>/dev/null | head -20)

Manual focus notes (edit before compact if needed):
- Current sprint: <fill via team>
- Active decisions: <fill via team>
EOF

exit 0

.claude/hooks/session-start-reinject.sh:

#!/bin/bash
# Re-inject latest snapshot only when session resumes from compact
INPUT=$(cat)
SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"')

if [ "$SOURCE" != "compact" ]; then exit 0; fi

LATEST=$(ls -t "$CLAUDE_PROJECT_DIR/.claude/snapshots/"*.md 2>/dev/null | head -1)
if [ -n "$LATEST" ]; then
  echo "## Restored context from pre-compact snapshot"
  cat "$LATEST"
fi
exit 0

.claude/settings.json:

{
  "hooks": {
    "PreCompact": [
      { "hooks": [
        { "type": "command",
          "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-compact-snapshot.sh" } ] }
    ],
    "SessionStart": [
      { "matcher": "compact",
        "hooks": [
          { "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start-reinject.sh" } ] }
    ]
  }
}

Pattern นี้ official docs เรียกว่า “compaction-resilient workflow” — PreCompact extract → compaction runs → SessionStart re-inject. ผมเทสกับ session ที่ยาว 8 ชั่วโมงต่อเนื่อง ลด context loss ที่ noticeable ได้ประมาณ 60-70% เทียบกับ session ที่ไม่มี hook คู่นี้.

ข้อควรระวัง: stdout จาก SessionStart hook ทั้งหมด จะถูก inject เข้า context — อย่าให้ snapshot โต. เก็บแค่ decisions + file paths สำคัญ ไม่ใช่ทั้ง git log.


Recipe 3 — PostToolUse (Write/Edit): invalidate cache ใน monorepo อัตโนมัติ

ปัญหา: ทีม dev ไทยส่วนมากที่ผมเจอใช้ Turborepo หรือ Nx เป็น monorepo orchestrator. พอ Claude แก้ไฟล์ใน packages/shared/, dependency graph ของ Turbo ควร invalidate cache ของ packages ที่ depend on shared — แต่ถ้า Claude ไม่ได้รัน build command, cache ไม่รู้ว่ามีไฟล์เปลี่ยน. ทำให้ developer ที่กลับมา pnpm dev คนถัดไปเจอผลเก่า.

Hook: PostToolUse กับ matcher Edit|Write|MultiEdit fires หลังทุกครั้งที่ Claude เขียนไฟล์.

Code: trigger turbo prune cache สำหรับ workspace ที่ถูกแก้

.claude/hooks/invalidate-turbo-cache.sh:

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
[ -z "$FILE_PATH" ] && exit 0

# Determine which workspace (packages/* or apps/*) the file belongs to
WORKSPACE=$(echo "$FILE_PATH" | grep -oE '(packages|apps)/[^/]+' | head -1)
[ -z "$WORKSPACE" ] && exit 0

# Turborepo: clear hashed cache entries for this workspace
PKG_JSON="$CLAUDE_PROJECT_DIR/$WORKSPACE/package.json"
[ ! -f "$PKG_JSON" ] && exit 0
PKG_NAME=$(jq -r '.name' "$PKG_JSON" 2>/dev/null)
[ -z "$PKG_NAME" ] && exit 0

# Remove .turbo cache for the workspace (Turbo will re-hash on next run)
rm -rf "$CLAUDE_PROJECT_DIR/$WORKSPACE/.turbo" 2>/dev/null
echo "✓ Invalidated turbo cache for ${PKG_NAME} (after ${FILE_PATH})"
exit 0

.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/invalidate-turbo-cache.sh",
            "background": true }
        ]
      }
    ]
  }
}

สำคัญ: ใช้ matcher pattern Edit|Write|MultiEdit ครอบทุกตัวที่เขียนไฟล์. ถ้าทีมใช้ Nx replace ด้วย npx nx reset --workspaces แทน. ถ้า Bun workspaces ใช้ bun pm cache rm.

Numbers สำหรับทีมไทย: สำหรับ monorepo ที่มี 12 packages ทีมหนึ่งที่ผมเทสด้วย hook นี้ช่วยลด “stale cache bug reports” จากประมาณ 4 ครั้ง/สัปดาห์ → 0 ภายใน 2 สัปดาห์ — ไม่ใช่จำนวนมาก แต่เป็น bug ที่ debug ยากที่สุดเพราะมัน reproduce ไม่ได้ใน CI.


Recipe 4 — ConfigChange: ป้องกัน security hooks ถูก disable แบบเงียบๆ

ปัญหา: ทีมตั้ง PreToolUse hook ไว้บล็อก rm -rf, block secret files, แต่ junior dev หรือ AI agent อื่นมาแก้ .claude/settings.json แล้วลบ security hook ออกแบบไม่เจตนา. กว่าจะรู้ตัวอาจมีไฟล์ลบไปแล้ว.

Hook: ConfigChange เป็นหนึ่งใน 3 hooks ที่ block ได้จริง — อีกสองตัวคือ PreToolUse และ PermissionRequest ตาม official reference. ใช้ exit code 2 เพื่อ reject การเปลี่ยนแปลง.

Code: log ทุกการเปลี่ยน + block ถ้าตัด security hook ออก

.claude/hooks/config-change-audit.sh:

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.file // ""')
OLD=$(echo "$INPUT" | jq -r '.old_content // ""')
NEW=$(echo "$INPUT" | jq -r '.new_content // ""')

AUDIT_LOG="$CLAUDE_PROJECT_DIR/.claude/config-audit.log"
echo "[$(date '+%Y-%m-%dT%H:%M:%S')] CHANGE in $FILE" >> "$AUDIT_LOG"

# Block: if old config had "PreToolUse" hook but new doesn't, reject
if echo "$OLD" | grep -q '"PreToolUse"' && ! echo "$NEW" | grep -q '"PreToolUse"'; then
  jq -n '{
    decision: "block",
    reason: "PreToolUse security hook cannot be removed without team review. Open a PR instead."
  }'
  exit 2
fi
exit 0

.claude/settings.json:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          { "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/config-change-audit.sh" }
        ]
      }
    ]
  }
}

Reality check: hook นี้ไม่ใช่ “การป้องกัน enterprise security” ที่บริษัทใหญ่ใช้ — สำหรับ enterprise grade ต้องใช้ managed settings + IT policy ตาม Claude Code admin docs. แต่สำหรับทีม 3-15 คน hook นี้ก็เพียงพอ + ให้ audit trail.

Tip: ถ้าใช้ Git ดี ๆ ทำ commit hook ใน Git ที่ require code review ของ .claude/settings.json ทุกครั้ง ก็เป็น layer 2. แต่ ConfigChange hook ทำงาน real-time ใน session ที่ Claude พยายามแก้เอง.


Recipe 5 — Elicitation: security gate สำหรับ MCP input requests

ปัญหา: MCP server หลายตัว (Notion, GitHub, Stripe MCPs) ใช้ “elicitation” — ขออินพุตจาก user ระหว่าง tool call เช่น ขอ confirm transaction, ขอ select project. ถ้าทีมเปิด auto-mode ไว้, Claude อาจจะตอบเรื่อยๆ ทำให้ MCP server ที่ malicious ขโมยข้อมูลผ่าน prompt ที่ดูปกติได้.

Hook: Elicitation fires เมื่อ MCP server เรียก elicitation. ใน Anthropic March 2026 update event นี้ block ได้ด้วย exit code 2 — เป็น real security gate.

Code: block elicitation จาก MCP servers ที่ไม่อยู่ใน allowlist

.claude/hooks/elicitation-gate.sh:

#!/bin/bash
INPUT=$(cat)
SERVER=$(echo "$INPUT" | jq -r '.mcp_server_name // ""')
PROMPT=$(echo "$INPUT" | jq -r '.elicitation_prompt // ""')

# Allowlist - edit per project
ALLOWED=("notion" "linear" "github-official")

ALLOWED_PATTERN=$(IFS='|'; echo "${ALLOWED[*]}")
if ! echo "$SERVER" | grep -qE "^(${ALLOWED_PATTERN})$"; then
  jq -n --arg s "$SERVER" '{
    decision: "block",
    reason: ("MCP server \"" + $s + "\" not in elicitation allowlist. Review .claude/hooks/elicitation-gate.sh.")
  }'
  exit 2
fi

# Even allowed servers: block if prompt mentions credentials
if echo "$PROMPT" | grep -qiE 'password|api[_-]?key|secret|token'; then
  jq -n '{
    decision: "block",
    reason: "Elicitation requesting credentials blocked. Use environment variables or secret manager."
  }'
  exit 2
fi
exit 0

.claude/settings.json:

{
  "hooks": {
    "Elicitation": [
      {
        "hooks": [
          { "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/elicitation-gate.sh" }
        ]
      }
    ]
  }
}

สำหรับทีมไทย: ถ้าทีมใช้ MCP server ของไทยเอง (Line OA MCP, K-API MCP) ใส่ใน allowlist ด้วย. แต่ keep allowlist สั้น — ทุก server ใน list คือ trust boundary ที่คุณรับรอง.


Deploy แบบ team-wide: 3 step

  1. เก็บ hooks ใน repo: ใส่ทุก script ใน .claude/hooks/ แล้ว commit เข้า git. ใส่ใน package.json postinstall ว่า chmod +x .claude/hooks/*.sh.
  2. แยก settings.json layers: ใช้ .claude/settings.json (project, commit) สำหรับ team hooks, .claude/settings.local.json (gitignored) สำหรับ per-developer override.
  3. เปิด /hooks ใน Claude Code เพื่อ verify ว่า hook ถูก load — ทุก event ควรเห็น count > 0. ถ้าไม่เห็น แปลว่า matcher ไม่ตรง.

ทีมที่อยากเริ่มเฉพาะ subset ผมแนะนำลำดับนี้: Recipe 1 (SubagentStop notification) → Recipe 3 (cache invalidate) → Recipe 2 (PreCompact) → Recipe 4 (ConfigChange) → Recipe 5 (Elicitation). ลำดับนี้ค่อยๆ ขยายจาก “ลด friction” ไป “เพิ่ม security” — เริ่มจากตัวที่ทีมยอมรับง่ายก่อน.


FAQ

ทำไม SubagentStop ของผมไม่ fire ทั้งที่ subagent จบงานแล้ว?

ตรวจ 3 จุด: (1) settings.json syntax ถูกต้อง — รัน /hooks ใน Claude Code ดูว่า SubagentStop มี count, (2) ถ้าใช้ subagent ใน skill หรือ subagent frontmatter, Stop hook ใน subagent file จะถูก convert เป็น SubagentStop อัตโนมัติ ตาม Anthropic docs, (3) script ต้อง executable — chmod +x .claude/hooks/*.sh.

PreCompact กับ PostCompact ต่างกันยังไง ใช้ตัวไหนดี?

PreCompact fires ก่อน compaction (เก็บ snapshot ของ state เดิม), PostCompact fires หลังเสร็จ (รู้ว่า compact เสร็จเรียบร้อย). Pattern ที่แนะนำในโพสต์นี้ใช้ PreCompact คู่กับ SessionStart matcher compact แทน PostCompact เพราะ stdout ของ SessionStart จะ inject เข้า context ของ Claude ทันที — ส่วน PostCompact ทำ logging ได้ดี แต่ inject context ไม่ได้.

ConfigChange hook block ได้จริงไหม หรือแค่ log อย่างเดียว?

Block ได้จริง. ConfigChange เป็น 1 ใน 3 hooks ที่ exit code 2 บล็อกการเปลี่ยนแปลงได้ (อีกสองคือ PreToolUse และ PermissionRequest). hook อื่นๆ เช่น PostToolUse, SessionStart เป็น observe-only เท่านั้น.

ทีมไทยที่ใช้ LINE OA จะ wire SubagentStop เข้า LINE ยังไง?

Replace Slack webhook ใน Recipe 1 ด้วย LINE Messaging API push message endpoint (https://api.line.me/v2/bot/message/push) พร้อม Authorization: Bearer $LINE_CHANNEL_ACCESS_TOKEN. ถ้าต้อง flex message ดูตัวอย่างใน skill plugin connector guide ของเรา.

Hook ทำให้ Claude Code ช้าลงไหม?

ขึ้นกับ hook. blocking hook ที่รันยาว (เช่น HTTP call 5 วินาที) ใน PreToolUse จะทำให้ทุก tool call ช้า. ทางออก: ตั้ง "background": true สำหรับ hook ที่ไม่ต้องการให้ Claude รอผล (notification, logging, cache invalidate) — feature นี้ Anthropic release มกราคม 2026. Security hooks ที่ block ต้องเป็น synchronous เสมอ.

ใช้ HTTP hooks แทน command hooks ดีไหม?

ดีถ้าทีมมี hook server กลาง (เช่น internal API ที่ทำ logging รวม). HTTP hook POST JSON ไปยัง endpoint แล้วรับ JSON กลับ. ส่วน command hook เร็วกว่าและ debug ง่ายกว่าสำหรับเริ่มต้น. ใน Thailand context, HTTP hook ดีสำหรับทีมที่มี shared infra; ทีมเล็กควรเริ่มที่ command hook ก่อน.


สรุปสั้น

5 recipes ในโพสต์นี้ — SubagentStop, PreCompact/SessionStart-compact, PostToolUse cache invalidate, ConfigChange, Elicitation — ครอบ “เคสที่ pre-commit hook ครอบไม่ได้” สำหรับทีม Thai dev ที่ใช้ Claude Code เป็นเครื่องมือหลัก. แต่ละ recipe มาพร้อม JSON config + bash script ที่ copy-paste ได้เลย. เริ่มจาก Recipe 1 ก่อน รอเห็นผล 1 สัปดาห์ ค่อยขยับ Recipe ถัดไป — ไม่จำเป็นต้องใส่ทั้ง 5 พร้อมกัน. คนที่ยังไม่ได้อ่าน Claude Code hooks 25 events 2026 แนะนำให้กลับไปอ่านเป็นฐานก่อน แล้วใช้ recipes ในโพสต์นี้เป็น “level 2” — pre-commit เป็น level 1, lifecycle ครบทั้ง suite คือ level 2.

ถ้าทีมคุณ deploy แล้วเจอ pattern ที่ work หรือ break ทักมาคุยใน comment ได้ ผมรวบรวมไว้ใน คู่มือ Claude Code ฉบับสมบูรณ์ 2026 ของ Data-Espresso เป็น living document.

Leave a Comment

สอบถามข้อมูล
Scroll to Top