SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-15

วิธีใช้งาน Claude Code hooks และการตั้งค่าคำสั่ง shell

ทำความเข้าใจการทำงานของ Claude Code hooks ที่รันคำสั่งอัตโนมัติไม่ว่าโมเดลจะเห็นด้วยหรือไม่ พร้อมวิธีใช้ exit code 2 เพื่อยกเลิก tool call และข้อควรระวังด้านความปลอดภัย

Claude Code hook คืออะไร

Claude Code hook คือคำสั่ง shell ที่ Claude Code เรียกใช้งานด้วยตนเอง ณ จุดที่กำหนดไว้ในวงจรการทำงาน นี่คือความแตกต่างทั้งหมดระหว่าง hook กับไฟล์กฎ (rules file) คำสั่งใน CLAUDE.md เป็นเพียงคำแนะนำที่โมเดลจะนำไปพิจารณาร่วมกับบริบทอื่นทั้งหมด แต่ hook คือโค้ดที่จะถูกเรียกใช้งานไม่ว่าโมเดลจะเห็นด้วยหรือไม่ก็ตาม หาก agent ของคุณยังคงข้ามการจัดรูปแบบ (formatter) ที่คุณสั่งไปแล้วสองครั้ง คุณไม่จำเป็นต้องเพิ่มความเข้มงวดของคำสั่ง แต่คุณต้องใช้ hook

กลไกนี้มีขนาดเล็ก คุณเพียงลงทะเบียนคำสั่งไว้ในไฟล์การตั้งค่าภายใต้ชื่อเหตุการณ์ (event name) เมื่อเหตุการณ์นั้นเกิดขึ้น Claude Code จะรันคำสั่งของคุณและเขียนข้อมูลเหตุการณ์ลงใน standard input (stdin) ในรูปแบบ JSON (JavaScript object notation) คำสั่งของคุณจะอ่านข้อมูลนั้น ดำเนินการตามที่กำหนด และตอบกลับด้วยสถานะการจบการทำงาน (exit status) การส่งค่า exit 2 จาก hook PreToolUse จะเป็นการยกเลิกการเรียกใช้เครื่องมือ (tool call) ก่อนที่จะเริ่มทำงาน และสิ่งที่สคริปต์ของคุณเขียนลงใน standard error (stderr) จะถูกส่งกลับไปยังโมเดลเพื่อใช้เป็นเหตุผลในการยกเลิก

ชื่อเหตุการณ์และชื่อฟิลด์ในที่นี้อ้างอิงจาก hooks reference ของ Claude Code ซึ่งตรวจสอบข้อมูล ณ เดือนสิงหาคม 2026 เทียบกับรุ่น 2.1.232 พื้นที่ส่วนนี้มีการเปลี่ยนแปลงอย่างรวดเร็ว ดังนั้นโปรดตรวจสอบข้อมูลอ้างอิงสำหรับเวอร์ชันของคุณก่อนที่จะคัดลอก JSON จากบล็อกโพสต์ใดๆ รวมถึงโพสต์นี้ด้วย คุณสามารถพิมพ์ข้อมูลของคุณออกมาได้ด้วย claude --version

ตำแหน่งที่ตั้งค่า hook

Hook คือบล็อก JSON ในไฟล์การตั้งค่า โดยสามารถเก็บไว้ได้ใน 6 ตำแหน่ง ซึ่งขอบเขตของไฟล์จะเป็นตัวกำหนดขอบเขตของ hook นั้นๆ

  • ~/.claude/settings.json: ทุกโปรเจกต์บนเครื่องของคุณ และไม่มีผลกับเครื่องอื่น
  • .claude/settings.json: หนึ่งโปรเจกต์ โดยจะถูก commit ลงใน repository เพื่อให้ทุกคนที่ clone ไปได้รับ hook นี้ด้วย
  • .claude/settings.local.json: หนึ่งโปรเจกต์ เฉพาะบนเครื่องของคุณเท่านั้น
  • การตั้งค่า Managed policy: มีผลทั้งองค์กร กำหนดโดยผู้ดูแลระบบ
  • hooks/hooks.json ภายในปลั๊กอิน จะทำงานขณะที่ปลั๊กอินนั้นเปิดใช้งานอยู่
  • Frontmatter ของ Skill หรือ subagent จะทำงานขณะที่คอมโพเนนต์นั้นเปิดใช้งานอยู่

รายการ hook จากไฟล์เหล่านี้จะถูกรวมเข้าด้วยกันแทนที่จะเขียนทับกัน ไฟล์การตั้งค่าโปรเจกต์จะเพิ่ม hook ของตนเองเข้าไปรวมกับ hook ในการตั้งค่าผู้ใช้ แทนที่จะแทนที่ของเดิม ดังนั้นเหตุการณ์เดียวจึงสามารถมี hook หลายรายการจากหลายไฟล์ได้ การตั้งค่า "disableAllHooks": true จะเป็นการปิดการทำงานของ hook ทั้งหมด ยกเว้นกรณีเดียวคือ hook จากการตั้งค่า managed policy จะยังคงทำงานต่อไป เว้นแต่ว่าจะมีการกำหนดค่าปิดการทำงานไว้ในการตั้งค่า managed ด้วยเช่นกัน

เรียกใช้ /hooks ภายใน session เพื่อแสดงรายการ hook ทั้งหมดที่ลงทะเบียนไว้ในปัจจุบัน โดยจัดกลุ่มตามเหตุการณ์ พร้อมระบุไฟล์ต้นทางและตัวจับคู่ (matcher) สำหรับแต่ละรายการ เมนูนี้เป็นแบบอ่านอย่างเดียว ดังนั้นคุณต้องแก้ไข hook โดยการแก้ไขไฟล์การตั้งค่าโดยตรง โดยปกติแล้วระบบติดตามไฟล์ (file watcher) จะตรวจพบการแก้ไขและอัปเดตให้โดยไม่ต้องเริ่มการทำงานใหม่

เหตุการณ์ hook ของ Claude Code มีอะไรบ้าง

Release 2.1.232 ระบุเหตุการณ์ไว้ 31 รายการ ตั้งแต่ SessionStart ไปจนถึง SessionEnd ซึ่งครอบคลุมการบีบอัดข้อมูล (compaction), subagents, worktrees และไฟล์การตั้งค่า งานฝั่งเซิร์ฟเวอร์จะใช้เพียงบางส่วนเท่านั้น

  • PreToolUse: ก่อนที่ tool call จะทำงาน นี่คือเหตุการณ์ที่สามารถบล็อกการทำงานได้
  • PostToolUse: หลังจาก tool call ทำงานสำเร็จ ส่วน PostToolUseFailure จะทำงานเมื่อเกิดความล้มเหลว ดังนั้น hook ที่ต้องการตรวจสอบทุกผลลัพธ์จำเป็นต้องใช้ทั้งสองรายการ
  • PermissionRequest: เมื่อ tool call ต้องการการตัดสินใจเรื่องสิทธิ์ ซึ่งเป็นจังหวะเดียวกับที่จะแสดง prompt ขออนุมัติ
  • UserPromptSubmit: เมื่อคุณส่ง prompt ก่อนที่ Claude จะประมวลผล สิ่งที่ hook นี้พิมพ์ออกมายัง stdout จะถูกเพิ่มเข้าไปในบริบท (context) ของโมเดล
  • SessionStart และ SessionEnd: ที่จุดเริ่มต้นและจุดสิ้นสุดของแต่ละเซสชัน นอกจากนี้ SessionStart จะทำงานหลังจากการบีบอัดข้อมูล (compaction) ภายใต้ค่า matcher คือ compact
  • Stop: เมื่อ Claude ตอบกลับเสร็จสิ้น ซึ่งจะทำงานหนึ่งครั้งต่อหนึ่งเทิร์น ไม่ใช่หนึ่งครั้งต่อหนึ่งงานที่เสร็จสมบูรณ์

ทุกกลุ่มจะมี matcher ที่ทำหน้าที่ตัดสินว่าเหตุการณ์ใดบ้างที่จะเรียกใช้ hook สำหรับเหตุการณ์ของเครื่องมือ (tool events) จะมีการกรองด้วยชื่อเครื่องมือ ดังนั้น "Edit|Write" จะทำงานเฉพาะเมื่อมีการแก้ไขไฟล์เท่านั้น ค่า matcher จะคำนึงถึงตัวพิมพ์เล็ก-ใหญ่ หากเว้นว่างไว้ hook จะทำงานในทุกเหตุการณ์ เครื่องมือจากเซิร์ฟเวอร์ MCP (model context protocol) จะถูกตั้งชื่อเป็น mcp__<server>__<tool> ดังนั้นการกำหนด matcher เป็น "mcp__github__.*" จะดักจับเฉพาะเครื่องมือของเซิร์ฟเวอร์นั้นและไม่ยุ่งกับเครื่องมืออื่น

hook ประเภท Stop มีกับดักที่ควรทราบก่อนเริ่มเขียน hook ประเภท Stop ที่บล็อกการทำงานจะส่งโมเดลกลับไปทำงานต่อ และ Claude Code จะยกเลิกการทำงานของ hook นั้นหลังจากบล็อกติดต่อกัน 8 ครั้ง โปรดอ่านฟิลด์ stop_hook_active จากข้อมูลขาเข้าของ hook และสั่ง exit 0 เมื่อเป็นจริง มิฉะนั้น hook ของคุณจะทำงานวนซ้ำจนกว่าจะถึงขีดจำกัดดังกล่าว

สิ่งที่ hook ได้รับผ่าน stdin

เมื่อ Claude กำลังจะรัน npm test ตัว hook PreToolUse บน Bash จะอ่านข้อมูลต่อไปนี้ผ่าน stdin:

{
  "session_id": "abc123",
  "cwd": "/home/deploy/myproject",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

ทุกเหตุการณ์จะประกอบด้วย session_id, cwd, permission_mode, transcript_path และ hook_event_name สำหรับเหตุการณ์ที่เกี่ยวข้องกับเครื่องมือ (tool events) จะมีการเพิ่ม tool_name, tool_input และ tool_use_id เข้ามาด้วย ส่วนเหตุการณ์อื่นๆ จะมีฟิลด์เฉพาะของตนเอง เช่น UserPromptSubmit จะได้รับข้อความ prompt และ SessionStart จะได้รับ source ของ startup, resume, clear, compact หรือ fork

jq เป็นวิธีปกติในการอ่านข้อมูลนี้ภายใน shell script แต่ image ของเซิร์ฟเวอร์แบบ minimal จะไม่มีเครื่องมือนี้ติดตั้งมาให้ ให้ติดตั้งก่อนด้วยคำสั่ง sudo apt install -y jq บน Ubuntu และ Debian

ผลลัพธ์ของ exit status ต่อการเรียกใช้เครื่องมือที่กำลังทำงานอยู่

มีผลลัพธ์ที่เป็นไปได้ 3 รูปแบบ:

  • Exit 0 หมายความว่า hook ของคุณไม่มีข้อโต้แย้ง ในกรณีของ PreToolUse นี่ไม่ใช่การอนุมัติ และกระบวนการตรวจสอบสิทธิ์ตามปกติจะยังคงทำงานต่อไป สำหรับ UserPromptSubmit และ SessionStart ข้อมูลใน stdout จะถูกเพิ่มเข้าไปในบริบทของโมเดล
  • Exit 2 จะระงับการดำเนินการในเหตุการณ์ที่สามารถระงับได้ ซึ่งรวมถึง PreToolUse โดย stderr จะกลายเป็นเหตุผลที่แสดงให้โมเดลเห็น สำหรับเหตุการณ์ที่ไม่สามารถระงับได้ เช่น PostToolUse การระงับจะถูกเพิกเฉย แม้ว่า stderr จะยังคงถูกส่งไปยังโมเดลเพื่อเป็นข้อมูลป้อนกลับก็ตาม
  • Exit code อื่นๆ ถือเป็นข้อผิดพลาดที่ไม่ขัดขวางการทำงาน การดำเนินการจะดำเนินต่อไป โดยบันทึกการสนทนาจะแสดงประกาศข้อผิดพลาดของ hook พร้อมกับบรรทัดแรกของ stderr ต่อจากข้อความ Failed with non-blocking status code:

หากต้องการทำมากกว่าแค่การระงับหรือนิ่งเฉย ให้ใช้ exit 0 แล้วพิมพ์ออบเจกต์ JSON ออกทาง stdout แทน โดย hook ประเภท PreToolUse จะตัดสินใจด้วย permissionDecision:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database drops go through a migration, not through the agent."
  }
}

"allow" จะข้ามการถามตอบแบบโต้ตอบ "deny" จะยกเลิกการเรียกและส่งเหตุผลไปยังโมเดล และ "ask" จะแสดงการถามตอบตามปกติ ให้เลือกเพียงรูปแบบเดียวต่อหนึ่ง hook การใช้ exit 2 ร่วมกับการตัดสินใจผ่าน JSON บน stdout จะทำให้ได้ผลลัพธ์ที่คุณต้องตรวจสอบย้อนหลัง

เมื่อมี hook หลายตัวตรงกับเหตุการณ์เดียวกัน hook เหล่านั้นจะทำงานแบบขนานและทุกตัวจะทำงานจนเสร็จสิ้น การใช้ deny จาก hook หนึ่งตัวจะไม่หยุดการทำงานของ hook อื่นๆ ดังนั้น hook สำหรับบันทึก log จะยังคงเขียนบรรทัดของมันได้แม้ว่า hook ประเภท guardrail จะปฏิเสธการเรียกนั้นก็ตาม จากนั้น Claude Code จะรวมคำตอบเข้าด้วยกันและเลือกผลลัพธ์ที่มีข้อจำกัดสูงสุด โดยเรียงลำดับจาก ปฏิเสธ (deny), เลื่อน (defer), ถาม (ask) ไปจนถึง อนุญาต (allow)

ตัวอย่างที่ 1: การบล็อกคำสั่งที่เป็นอันตรายก่อนเริ่มทำงาน

บันทึกไฟล์นี้เป็น .claude/hooks/block-destructive.sh ในโปรเจกต์ของคุณ:

#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
  if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
    echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
    exit 2
  fi
done

exit 0

ทำให้ไฟล์สามารถรันได้ จากนั้นลงทะเบียนไว้ที่ PreToolUse ใน .claude/settings.json:

chmod +x .claude/hooks/block-destructive.sh
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
            "timeout": 10,
            "statusMessage": "Checking the command against policy"
          }
        ]
      }
    ]
  }
}

ทดสอบสคริปต์ด้วยตนเองก่อนที่จะใช้งานจริง เพราะหาก hook เกิดข้อผิดพลาดขณะประมวลผล input ของตัวเอง ระบบจะอนุญาตให้คำสั่งผ่านไปได้ (fails open):

echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
  | .claude/hooks/block-destructive.sh
echo $?

คุณควรเห็นบรรทัด Blocked by policy: ปรากฏบน stderr และได้รับ exit code เป็น 2 ลองทดสอบด้วยคำสั่งที่ไม่เป็นอันตราย เช่น ls -la ซึ่งคุณไม่ควรเห็นผลลัพธ์ใดๆ และได้รับ exit code เป็น 0 ในระหว่างเซสชัน การเรียกใช้คำสั่งที่ถูกปฏิเสธจะปรากฏในบันทึกพร้อมกับข้อความเหตุผลที่คุณระบุไว้ ซึ่งโมเดลจะอ่านข้อความนั้นและปรับเปลี่ยนพฤติกรรม

คุณสมบัติประการหนึ่งที่ทำให้สิ่งนี้คุ้มค่าคือ hook แบบ PreToolUse จะทำงานก่อนการตรวจสอบ permission-mode ในทุกโหมด ดังนั้นการปฏิเสธคำสั่งจึงมีผลแม้จะอยู่ในโหมด bypassPermissions นี่คือสิ่งที่ทำให้ hook มีประโยชน์เมื่อใช้งานร่วมกับ Claude Code auto mode และการตั้งค่าสิทธิ์ ซึ่งแม้จะมีการลดระดับการแจ้งเตือนลง แต่ hook ก็ยังคงทำงานอยู่

โปรดเข้าใจข้อจำกัดของวิธีนี้ การใช้ pattern matching กับสตริงคำสั่งเป็นเพียงมาตรการป้องกันไม่ให้เอเจนต์ทำงานโดยประมาทเท่านั้น ไม่ใช่การป้องกันเอเจนต์ที่พยายามหลบเลี่ยง เนื่องจากคำสั่งเดียวกันสามารถเขียนในรูปแบบที่ grep ของคุณตรวจไม่พบได้ กฎที่เข้มงวดควรอยู่ในระบบจัดการสิทธิ์ (permission system) และในบัญชีผู้ใช้ที่กระบวนการนั้นทำงานอยู่เท่านั้น

ตัวอย่างที่ 2: การจัดรูปแบบและตรวจสอบ lint หลังการแก้ไขทุกครั้ง

PostToolUse ที่มีตัวจับคู่ Edit|Write จะทำงานหลังจากเครื่องมือแก้ไขไฟล์ใดๆ ทำงานเสร็จสิ้น ให้บันทึกสิ่งนี้เป็น .claude/hooks/after-edit.sh:

#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0

case "$FILE" in
  *.py)
    ruff format "$FILE" >/dev/null 2>&1
    if ! ruff check "$FILE" >&2; then
      exit 2
    fi
    ;;
  *.sh)
    if ! shellcheck "$FILE" >&2; then
      exit 2
    fi
    ;;
esac

exit 0
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

ลองให้ Claude เพิ่มฟังก์ชันที่มีการเยื้องบรรทัดผิดพลาดลงในไฟล์ Python จากนั้นเปิดไฟล์นั้นขึ้นมา คุณจะพบว่าไฟล์ถูกจัดรูปแบบเรียบร้อยแล้ว นั่นคือการตรวจสอบว่า hook ทำงานแล้ว เพราะหาก hook ทำงานสำเร็จ จะไม่มีข้อความใดๆ ปรากฏในบทสนทนา

การใช้ exit 2 ในที่นี้ไม่ได้เป็นการย้อนกลับการเปลี่ยนแปลงใดๆ PostToolUse จะทำงานหลังจากเครื่องมือได้ดำเนินการไปแล้ว ดังนั้นการแก้ไขจึงถูกบันทึกลงในดิสก์ไม่ว่ากรณีใดก็ตาม สิ่งที่ exit 2 มอบให้คุณคือการทำให้ผลลัพธ์ของ ruff check ส่งกลับไปยังโมเดลในฐานะข้อมูลป้อนกลับ เพื่อให้โมเดลแก้ไขข้อผิดพลาดที่เพิ่งเกิดขึ้นแทนที่จะดำเนินการต่อไป นี่คือความแตกต่างระหว่างความล้มเหลวของ lint ที่คุณพบในขั้นตอน commit กับความล้มเหลวที่เอเจนต์ซ่อมแซมได้ในรอบการทำงานเดียวกัน

มีข้อจำกัดของตัวจับคู่สองประการที่สำคัญในที่นี้ Edit|Write จะไม่เห็นไฟล์ที่ถูกเปลี่ยนแปลงโดยคำสั่ง shell และ Claude มักเขียนไฟล์ผ่าน Bash บ่อยพอที่ช่องว่างนี้จะกลายเป็นปัญหาจริง สำหรับการครอบคลุมในระดับต่อการเรียกใช้งาน ให้จับคู่ Bash ด้วย และให้สคริปต์แสดงรายการไฟล์ที่เปลี่ยนแปลงด้วย git status --porcelain สำหรับการครอบคลุมในระดับต่อรอบการทำงาน ให้ใส่การสแกนไว้ใน hook Stop แทน

ตัวอย่างที่ 3: บันทึกทุกการเรียกใช้เครื่องมือเพื่อการตรวจสอบ

ตัวจับคู่ (matcher) ที่ว่างเปล่าใน PostToolUse จะทำงานกับทุกเครื่องมือ การส่งบันทึกไปยัง system journal แทนที่จะเป็นไฟล์ใน home directory จะช่วยป้องกันไม่ให้ตัว agent เข้าถึงหรือแก้ไขบันทึกนั้นได้ผ่าน shell ของตัวเอง:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
          }
        ]
      }
    ]
  }
}

อ่านบันทึกกลับมาด้วย journalctl -t claude-code -o cat | tail -n 5 คุณควรจะเห็นข้อมูล JSON หนึ่งบรรทัดต่อการเรียกใช้เครื่องมือหนึ่งครั้ง โดยข้อมูลล่าสุดจะอยู่ท้ายสุด หากไม่มีข้อมูลปรากฏขึ้น แสดงว่า hook ไม่ทำงาน ซึ่งสามารถดูวิธีแก้ไขได้ในส่วนการแก้ไขปัญหาด้านล่าง

ให้เพิ่มบล็อกเดียวกันนี้ภายใต้ PostToolUseFailure เพื่อบันทึกการเรียกใช้ที่ล้มเหลว เนื่องจาก PostToolUse จะทำงานเฉพาะเมื่อการเรียกใช้สำเร็จเท่านั้น แต่คำสั่งที่ล้มเหลวมักเป็นสิ่งที่น่าสนใจกว่า เหตุผลที่ใช้ logger แทนการเขียนต่อท้ายไฟล์ใน home directory ของคุณคือเรื่องสิทธิ์การเข้าถึง (ownership): hook จะทำงานในฐานะผู้ใช้เดียวกับ shell ของ agent ดังนั้นไฟล์ใดก็ตามที่ผู้ใช้นั้นสามารถเขียนต่อท้ายได้ ผู้ใช้นั้นก็สามารถลบเนื้อหาทิ้งได้เช่นกัน ในขณะที่ journal จะถูกเขียนโดย systemd-journald ภายใต้บัญชีของระบบเอง

ระยะเวลาที่ hook สามารถทำงานได้

ChartDefault hook timeout in seconds, by hook type and event
The data behind this chart
[
  {
    "label": "command, http or mcp_tool hook",
    "default_timeout_seconds": 600
  },
  {
    "label": "agent hook",
    "default_timeout_seconds": 60
  },
  {
    "label": "prompt hook",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on UserPromptSubmit",
    "default_timeout_seconds": 30
  },
  {
    "label": "command hook on MessageDisplay",
    "default_timeout_seconds": 10
  },
  {
    "label": "any hook on SessionEnd",
    "default_timeout_seconds": 1.5
  }
]

โดยปกติแล้ว command hook จะมีเวลาทำงานเริ่มต้นที่ 600 วินาที หรือเท่ากับ 10 นาที อย่างไรก็ตาม บางเหตุการณ์จะจำกัดเวลาให้สั้นลงอย่างมาก โดย hook ประเภท SessionEnd จะต้องใช้ทรัพยากรเวลาร่วมกันในงบรวมที่ 1.5 วินาที ดังนั้นการทำความสะอาดข้อมูลเมื่อสิ้นสุดเซสชันจึงต้องดำเนินการอย่างรวดเร็ว แม้ว่าการตั้งค่า timeout ที่ยาวขึ้นให้กับ hook จะช่วยเพิ่มงบเวลารวมดังกล่าวให้สูงขึ้นได้สูงสุดถึง 60 วินาทีก็ตาม

หาก hook ทำงานเกินระยะเวลาที่กำหนด ระบบจะยกเลิกการทำงานนั้นและไม่มีการตัดสินใจใดๆ เกิดขึ้น สำหรับ guardrail ประเภท PreToolUse หมายความว่า hook จะไม่ขัดขวางการทำงาน: การเรียกใช้เครื่องมือ (tool call) จะดำเนินต่อไปตามขั้นตอนการตรวจสอบสิทธิ์ปกติ ด้วยเหตุนี้จึงควรเขียนสคริปต์ guardrail ให้มีขนาดเล็ก สำหรับงานที่ใช้เวลานานและไม่มีใครรอผลลัพธ์ เช่น การส่ง log ไปยังปลายทาง ให้ตั้งค่า "async": true เพื่อให้ hook ทำงานในเบื้องหลังโดยไม่ขัดจังหวะการเรียกใช้เครื่องมือ

Hooks, rules files, skills และ MCP servers

มีสี่สิ่งที่มักสับสนกันเพราะทั้งหมดล้วนเปลี่ยนพฤติกรรมของ agent แต่มีเพียงสิ่งเดียวเท่านั้นที่ไม่ใช่แค่คำแนะนำ

Rules file (CLAUDE.md หรือไฟล์ภายใต้ .claude/rules/) คือข้อความที่ถูกโหลดเข้าสู่บริบทของโมเดล มันช่วยกำหนดพฤติกรรมแต่ไม่ได้บังคับใช้สิ่งใด เมื่อเทียบกับบทสนทนาที่ยาวเหยียด, diff ขนาดใหญ่ และคำขอใหม่จากผู้ใช้ ข้อความเพียงบรรทัดเดียวในไฟล์อาจถูกมองข้ามได้ นี่คือกลไกปกติที่อยู่เบื้องหลัง การที่ agent เพิกเฉยต่อคำสั่งที่คุณเขียนไว้

Skill คือโฟลเดอร์ที่เก็บคำสั่งและสคริปต์ซึ่งโมเดลจะโหลดขึ้นมาเมื่อพิจารณาว่า skill นั้นเกี่ยวข้อง การตัดสินใจดังกล่าวคือจุดประสงค์ของ skill และในขณะเดียวกันก็เป็นข้อจำกัด เพราะโมเดลยังคงเป็นผู้ตัดสินใจเอง คุณสามารถเห็นทั้งสองด้านได้ใน skill อย่าง Ponytail ซึ่งผลักดันให้ agent เลือกการเปลี่ยนแปลงที่เล็กที่สุดที่ใช้งานได้จริง เพราะมันกำหนดวิธีการรับมือกับงานทั้งหมดในแบบที่ hook ทำไม่ได้ และจะทำงานก็ต่อเมื่อโมเดลเลือกที่จะโหลดมันเท่านั้น

MCP (model context protocol) server มอบเครื่องมือใหม่ให้โมเดลเรียกใช้ มันขยายขอบเขตสิ่งที่ agent สามารถเข้าถึงได้ แต่มันไม่ได้บังคับให้ agent ต้องเรียกใช้สิ่งใด และมันเป็นกระบวนการแยกต่างหากที่คุณต้องดูแล ซึ่งเป็นงานเฉพาะทาง: ดูที่ การรัน MCP servers บน VPS

Hook เป็นเพียงสิ่งเดียวจากทั้งสี่ที่ทำงานโดยที่โมเดลไม่ต้องเป็นผู้เลือก ใช้ rules file สำหรับความต้องการทั่วไป และใช้ skill สำหรับขั้นตอนที่โมเดลควรปฏิบัติตามเมื่อถึงเวลาที่เหมาะสม ใช้ hook สำหรับขั้นตอนที่ต้องเกิดขึ้นทุกครั้ง หรือสิ่งที่ห้ามเกิดขึ้นโดยเด็ดขาด การเปรียบเทียบเชิงลึก รวมถึงกรณีที่ skill มีประสิทธิภาพเหนือกว่า rules file อยู่ใน การเปรียบเทียบระหว่าง skills, MCP และ rules files

Plugin เป็นเพียงรูปแบบการจัดแพ็กเกจ ไม่ใช่กลไกที่ห้า มันทำหน้าที่รวม hook เข้ากับ skill ให้เป็นหน่วยเดียวที่ติดตั้งได้ ซึ่งเป็นวิธีที่ทีมงานใช้ส่งมอบ guardrail ชุดเดียวกันไปยังทุกเครื่อง: ดูที่ วิธีการทำงานของ Claude Code plugins

การตัดสินใจด้านความปลอดภัยบน VPS ที่ใช้งานร่วมกัน

Hook คือโค้ดที่ agent เรียกใช้งาน โดยจะทำงานในฐานะผู้ใช้ที่เริ่มการทำงานของ Claude Code ซึ่งจะได้รับสิทธิ์และสภาพแวดล้อมของผู้ใช้นั้นมาทั้งหมด บนแล็ปท็อปประเด็นนี้เป็นเรื่องของเวิร์กโฟลว์ แต่บน VPS ที่ agent ทำงานโดยไม่มีผู้ดูแล ประเด็นนี้กลายเป็นเรื่องความปลอดภัยที่มีองค์ประกอบในทางปฏิบัติ 4 ประการ

Hook ใน repository คือโค้ดที่คุณไม่ได้เขียนเอง .claude/settings.json ถูก commit ไว้ ดังนั้นการ clone repository และเริ่ม session ภายในนั้นอาจเป็นการลงทะเบียน hook ที่มาพร้อมกับ repository ได้ Claude Code จะกั้น hook ของโปรเจกต์ไว้หลังกล่องโต้ตอบความเชื่อมั่นของ workspace สำหรับโฟลเดอร์นั้น ซึ่งหมายความว่าการยอมรับความเชื่อมั่นคือช่วงเวลาที่คุณตัดสินใจที่จะเรียกใช้ hook เหล่านั้น โปรดอ่านบล็อก hooks ก่อน

Hook สามารถมองเห็น input ของเครื่องมือทั้งหมด Audit hook ที่บันทึก tool_input จะเขียนอาร์กิวเมนต์ทุกตัวของทุกคำสั่งลงในไฟล์ รวมถึง token ใดก็ตามที่อาจปรากฏอยู่บนบรรทัดคำสั่ง log นั้นจึงต้องการการป้องกันในระดับเดียวกับ secret ซึ่งเป็นส่วนหนึ่งของปัญหาที่กว้างกว่าเรื่อง การเก็บ secret ให้พ้นจากมือของ AI agent

Hook สามารถเขียนข้อมูลลงในบริบทของโมเดลได้ สิ่งใดก็ตามที่ hook ประเภท SessionStart หรือ UserPromptSubmit พิมพ์ออกมายัง stdout จะถูกเพิ่มเข้าไปในการสนทนา Hook ที่ดึงข้อความจากภายนอก เช่น จาก issue tracker หรือไฟล์ log คือการส่งข้อความที่ไม่น่าเชื่อถือให้กับโมเดลเสมือนว่าคุณเป็นผู้พิมพ์เอง ให้ถือว่า stdout นั้นเป็น input มากกว่าที่จะเป็น output

สิทธิ์การเข้าถึงคือการควบคุมที่แท้จริง ให้รัน agent ในฐานะผู้ใช้เฉพาะที่ไม่มีสิทธิ์พิเศษและมีเพียงกฎ sudo ที่จำเป็นเท่านั้น การตั้งค่า PreToolUse deny เป็นสิ่งที่ควรมี โดยการออกแบบแล้วมันเป็นความพยายามที่ดีที่สุด (best effort): เอกสารอ้างอิงระบุไว้เช่นเดียวกันเกี่ยวกับตัวกรอง if และแนะนำให้ใช้ระบบ permission เมื่อคุณต้องการการปฏิเสธที่เด็ดขาด กฎ permission และบัญชีผู้ใช้ที่กระบวนการนั้นรันอยู่คือส่วนที่ยังคงความปลอดภัยได้จริงภายใต้สภาวะกดดัน

คุณสมบัติหนึ่งที่คงอยู่เสมอในทุกการกำหนดค่าคือ Hook ประเภท PreToolUse จะทำงานก่อนการตรวจสอบ permission-mode ในทุกโหมด ดังนั้น hook ที่ส่งค่ากลับเป็น deny จะบล็อกเครื่องมือแม้ว่าจะอยู่ในโหมด bypassPermissions ก็ตาม Hook สามารถจำกัดสิ่งที่กฎ permission อนุญาตให้ทำได้ แต่ไม่สามารถขยายสิทธิ์เหล่านั้นให้กว้างขึ้นได้

ทำไม hook ของฉันถึงไม่ทำงาน?

ให้ตรวจสอบตามลำดับขั้นตอนดังต่อไปนี้ แต่ละขั้นตอนจะระบุถึงอาการที่คุณอาจพบ:

  • รัน /hooks แล้วตรวจสอบว่า hook ปรากฏอยู่ภายใต้เหตุการณ์ที่คุณคาดหวังหรือไม่ หาก hook หายไปจากเมนู มักหมายความว่าไฟล์การตั้งค่ามีข้อผิดพลาดทางไวยากรณ์ JSON เนื่องจากไม่อนุญาตให้มีเครื่องหมายจุลภาคต่อท้าย (trailing comma) หรือความคิดเห็น (comment) หรือไฟล์ไม่ได้อยู่ในหนึ่งในหกตำแหน่งที่ระบุไว้ข้างต้น
  • เปรียบเทียบตัวจับคู่ (matcher) กับชื่อเครื่องมือให้ตรงกันทุกประการ ตัวจับคู่มีความไวต่อตัวพิมพ์เล็ก-ใหญ่ ดังนั้น "bash" จะไม่ตรงกับเครื่องมือ Bash
  • รันสคริปต์ด้วยตนเองโดยใช้ข้อมูลตัวอย่างตามตัวอย่างที่ 1 ด้านบน หากได้รับ exit code ที่ไม่คาดคิด แสดงว่าเป็นบั๊กในสคริปต์ของคุณ และ Claude Code จะรายงานว่าเป็นข้อผิดพลาดของ hook แทนที่จะเป็นคำตัดสิน
  • การแจ้งเตือนที่ระบุว่า jq: command not found หมายความว่า jq ไม่มีอยู่ในเครื่องนั้น ส่วน command not found สำหรับสคริปต์ของคุณเองหมายความว่า path ไม่สามารถระบุตำแหน่งได้ ดังนั้นให้ใช้ ${CLAUDE_PROJECT_DIR} หรือใช้ absolute path หากสคริปต์ไม่ทำงานเลย อาจเป็นเพราะไม่ได้ตั้งค่าให้เป็นไฟล์ที่รันได้ (executable)
  • hook พิมพ์ JSON ที่ถูกต้องออกมาแต่ไม่มีอะไรเกิดขึ้น hook รูปแบบ shell จะรันผ่าน sh -c และหาก shell profile ของคุณมีการพิมพ์ข้อความต้อนรับ (banner) ข้อความนั้นจะถูกแทรกไว้หน้า JSON ของคุณ ทำให้ stdout ไม่ได้เริ่มต้นด้วย { อีกต่อไป Claude Code จึงอ่านข้อมูลทั้งหมดเป็นข้อความธรรมดาและเพิกเฉยต่อคำตัดสินนั้น หาก exit code เป็น 0 จะไม่มีการรายงานใดๆ ยกเว้นใน debug log ให้ครอบ echo ใน profile ของคุณเพื่อให้รันเฉพาะใน interactive shell เท่านั้น
  • หากยังติดปัญหา: ให้เริ่มเซสชันด้วย claude --debug-file /tmp/claude.log และรัน tail -f /tmp/claude.log ในเทอร์มินัลที่สอง debug log จะบันทึกว่า hook ใดบ้างที่ตรงกัน, แต่ละ hook ส่งคืน exit code ใด, และบันทึกทุกสิ่งที่ hook เขียนลงใน stdout และ stderr

FAQ

ความแตกต่างระหว่าง hook ของ Claude Code กับคำสั่งใน CLAUDE.md คืออะไร?

คำสั่งใน CLAUDE.md คือข้อความที่อยู่ในบริบทของโมเดล จึงต้องแย่งพื้นที่ความสนใจกับบทสนทนาและคำขอปัจจุบัน ซึ่งโมเดลสามารถนำไปชั่งน้ำหนักเทียบกันได้ ส่วน hook คือคำสั่ง shell ที่ Claude Code จะเรียกใช้ ณ จุดที่กำหนดไว้ในวงจรการทำงาน ดังนั้นมันจะทำงานทุกครั้งที่เกิดเหตุการณ์นั้นโดยไม่ขึ้นอยู่กับการตัดสินใจของโมเดล ให้ใช้คำสั่งในไฟล์สำหรับความต้องการทั่วไป และใช้ hook สำหรับขั้นตอนที่ต้องเกิดขึ้นเสมอหรือการกระทำที่ต้องห้ามเกิดขึ้นเด็ดขาด

จะหยุดไม่ให้ Claude Code รันคำสั่ง shell บางอย่างได้อย่างไร?

ให้ลงทะเบียน hook ประเภท PreToolUse พร้อมกับตัวจับคู่ Bash ที่อ่านคำสั่งจาก .tool_input.command จากนั้นเขียนเหตุผลลงใน stderr แล้ว exit 2 ออกมา Claude Code จะยกเลิกการเรียกนั้นและแสดงเหตุผลของคุณให้โมเดลเห็น ซึ่งขั้นตอนนี้จะเกิดขึ้นก่อนการตรวจสอบในโหมด permission ดังนั้นการปฏิเสธจะมีผลแม้ในโหมด bypassPermissions การจับคู่รูปแบบ (pattern matching) บนสตริงคำสั่งเป็นเพียงมาตรการป้องกันเบื้องต้น ไม่ใช่ขอบเขตความปลอดภัยที่สมบูรณ์ เพราะคำสั่งเดียวกันสามารถเขียนในรูปแบบที่ตัวจับคู่มองไม่เห็นได้ จึงควรเสริมด้วยกฎการอนุญาต (permission rules) และใช้งานผ่านบัญชีผู้ใช้ที่ไม่มีสิทธิ์ระดับสูง

hook ของฉันพิมพ์ JSON ที่ถูกต้องแล้วแต่ไม่มีอะไรเกิดขึ้น เพราะเหตุใด?

สาเหตุที่พบบ่อยที่สุดคือ shell profile ของคุณ hook ที่ไม่มีฟิลด์ args จะทำงานผ่าน sh -c และ profile บางตัวจะพิมพ์ข้อความต้อนรับ (banner) ออกมาทุกครั้งที่เปิด shell ซึ่งข้อความนี้จะไปปรากฏบน stdout ก่อนหน้า JSON ของคุณ เนื่องจากผลลัพธ์ไม่ได้ขึ้นต้นด้วย { อีกต่อไป Claude Code จึงมองว่าทั้งหมดเป็นข้อความธรรมดาและเพิกเฉยต่อการตัดสินใจนั้น และหาก exit 0 ก็จะไม่มีการรายงานใดๆ ใน transcript เลย ให้ป้องกันการพิมพ์ echo ใน profile ของคุณด้วยการตรวจสอบว่าเป็น interactive-shell หรือไม่ จากนั้นยืนยันการแก้ไขโดยอ่าน debug log จาก claude --debug-file /tmp/claude.log

การรัน hook ของ Claude Code บนเซิร์ฟเวอร์ที่ใช้งานร่วมกันมีความปลอดภัยหรือไม่?

hook จะทำงานในฐานะผู้ใช้ที่เริ่ม Claude Code โดยใช้สิทธิ์การเข้าถึงไฟล์ของผู้ใช้นั้น ดังนั้น hook สามารถทำทุกอย่างที่บัญชีนั้นทำได้ สองแนวทางปฏิบัติที่ช่วยลดความเสี่ยงส่วนใหญ่คือ: รัน agent ด้วยบัญชีผู้ใช้เฉพาะที่ไม่มีสิทธิ์ระดับสูงและมีนโยบาย sudo ที่จำกัด และอ่านบล็อก hooks ของ repository ใดๆ ก่อนที่คุณจะยอมรับกล่องโต้ตอบความเชื่อถือใน workspace เพราะ project hook จะมาพร้อมกับ .claude/settings.json ให้ตั้งค่า "disableAllHooks": true ในไฟล์การตั้งค่าของคุณหากคุณไม่ต้องการให้ hook ใดๆ ทำงานเลย