บล็อก

Hooks ใน Claude Code 2026: ตั้งค่าทีละขั้นตอนพร้อมตัวอย่าง 7 รายการที่พร้อมใช้

Markdown

7 hooks พร้อมใช้สำหรับ .claude/settings.json คัดลอกและวางได้ทันที: การป้องกัน .env, prettier, การ force-push ไปยัง main, em dash และเซิร์ฟเวอร์ dev มี handler 4 ประเภทและ event 31 รายการ

อ่านบทความฉบับเต็ม

Hooks ใน Claude Code คือคำสั่ง shell การเรียก HTTP หรือการตรวจสอบด้วย LLM สั้น ๆ ที่ทำงานโดยอัตโนมัติก่อนหรือหลังการกระทำของ Claude Hooks เพิ่มการตรวจเฉพาะ tool events จัดรูปแบบหลังแก้ และใส่บริบทตอนเริ่มเซสชัน PreToolUse ที่ตรง matcher ปฏิเสธคำขอนั้นได้ แต่ตัวอย่าง regex เป็นตัวช่วยกันพลาด ไม่ใช่ขอบเขตปลอดภัยครอบคลุมทุกเส้นทางไฟล์หรือ Git ต่อไปนี้เป็นรายละเอียดของ handler 4 ประเภท, event ตลอดวงจรการทำงาน 31 รายการ, แบบทดสอบว่า “กฎควรอยู่ใน hook เมื่อใด”, hooks พร้อมคัดลอกไปใช้ 7 รายการสำหรับ .claude/settings.json และข้อผิดพลาด 3 ข้อที่ฉันเคยเจอด้วยตัวเอง

เอกสาร Ailoot: API, คีย์ และการตั้งค่า.

Hooks ใน Claude Code คืออะไร?

ประเด็นสำคัญ

Hook คือคำสั่ง shell การเรียก HTTP พรอมป์ต์ LLM สั้น ๆ หรือ subagent ที่ Claude Code เรียกใช้เพื่อตอบสนองต่อ event เฉพาะในวงจรการทำงาน ต่างจากคำสั่งใน CLAUDE.md ซึ่งโมเดลอาจเพิกเฉยหรือลืมได้ hook จะทำงานเหมือนเดิมทุกครั้ง เพราะมันคือโค้ด ไม่ใช่คำแนะนำ

ตลอดครึ่งปี ฉันเก็บกฎ “ห้ามแก้ไข .env โดยตรง” ไว้ใน CLAUDE.md Claude ละเมิดกฎนี้เดือนละครั้ง ไม่ใช่เพราะเจตนาร้าย แต่เพียงลืมไปเมื่อเซสชันยาวนาน ครั้งหนึ่งมีโทเค็นเกินมาใน .env; อีกครั้งหนึ่งตัวแปรชื่อซ้ำไปเขียนทับตัวแปรที่ใช้งานอยู่

เมื่อเครื่องมือแก้โดยตรงที่ตรง matcher ส่ง path .env hook ที่ตั้งจะปฏิเสธคำขอนั้น แต่ Bash redirection สคริปต์ MCP และเส้นทางอื่นต้องมีการควบคุมแยก ตัวอย่างนี้ไม่ได้ทำให้ไฟล์เปลี่ยนไม่ได้

Hooks คือคำสั่ง shell, HTTP endpoint หรือพรอมป์ต์ LLM ที่คุณกำหนด แล้วให้ทำงานโดยอัตโนมัติ ณ จุดต่าง ๆ ในวงจรการทำงานของ Claude Code

— Anthropic, https://code.claude.com/docs/en/hooks

กล่าวอีกอย่างคือ hook เป็นคำสั่งของคุณที่ Claude Code เรียกใช้เมื่อเกิด event บางอย่าง การแก้ไขไฟล์ การเปิด Bash การเริ่มเซสชัน หรือการทำงานของ subagent เสร็จ ล้วนเป็น event ที่คุณผูก hook เข้าด้วยได้

เหตุใดเรื่องนี้จึงสำคัญสำหรับคนเขียนโค้ดแบบ vibe coding:

  • ป้องกันไฟล์ที่มีข้อมูลลับจากการแก้ไขโดยไม่ตั้งใจ
  • เรียก prettier/eslint โดยอัตโนมัติหลังสร้างโค้ดทุกครั้ง
  • บล็อก git push --force ไปยัง main หากไม่ได้รับอนุญาตอย่างชัดเจน
  • ใส่บริบทล่าสุดเมื่อเริ่มแต่ละเซสชัน
  • บันทึกการกระทำของ Claude เพื่อการตรวจสอบ

CLAUDE.md จัดการบริบท ส่วน hooks จัดการ การกระทำ นี่เป็นคนละเรื่องกัน และการสับสนระหว่างสองอย่างนี้เป็นข้อผิดพลาดหลักของมือใหม่

Hooks ต่างจาก CLAUDE.md และ skills อย่างไร?

ประเด็นสำคัญ

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

เอกสารของ Anthropic อธิบายความแตกต่างระหว่าง CLAUDE.md กับ hooks ไว้อย่างชัดเจน:

เนื้อหา CLAUDE.md จะถูกส่งมาเป็นข้อความจากผู้ใช้หลัง system prompt ไม่ได้เป็นส่วนหนึ่งของ system prompt Claude อ่านและพยายามทำตาม แต่ไม่มีการรับประกันว่าจะปฏิบัติตามอย่างเคร่งครัด

— Anthropic, เอกสารเกี่ยวกับ memory, https://code.claude.com/docs/en/memory

กล่าวอีกอย่างคือ เนื้อหาใน CLAUDE.md ส่งถึง Claude ในฐานะข้อความแรกจากผู้ใช้ ไม่ใช่คำสั่งระบบ โมเดลจะอ่านและพยายามทำตาม แต่ไม่มีหลักประกัน โดยเฉพาะช่วงท้ายของเซสชันยาว ๆ เมื่อบริบทเพิ่มขึ้นและคำสั่งเก่าถูกดันออกไป

Hook ทำงานต่างออกไป มันอยู่ใน settings.json ไม่ขึ้นกับโมเดล และกระบวนการ Claude Code จะเรียกใช้เมื่อเกิด event เฉพาะ เช่น PreToolUse, PostToolUse, SessionStart และอื่น ๆ คำสั่งจะทำงานก่อน Claude ได้รับผลลัพธ์ และสามารถบล็อกการกระทำจริง ๆ (exit 2) หรือแก้ไขไฟล์หลังการแก้ไข (exit 0)

เปรียบเทียบ:

คุณสมบัติCLAUDE.mdHookSkill
อยู่ที่ไหนCLAUDE.md ในรูทโปรเจกต์.claude/settings.jsonโฟลเดอร์ .claude/skills/
ใครเป็นผู้เรียกใช้โมเดลอ่านเป็นส่วนหนึ่งของข้อความผู้ใช้กระบวนการ Claude Code เมื่อเกิด eventโมเดลผ่านเครื่องมือ Skill
รับประกันการทำงานหรือไม่ไม่ (พยายามอย่างดีที่สุด)command hook ที่ตั้งและ matcher ตรงรันโค้ด แต่ความล้มเหลวกับ exit ขึ้นกับ eventผ่านการเรียกโมเดล (มีการอนุญาต แต่ไม่ใช่ระดับ shell)
ใช้ทำอะไรบริบทโปรเจกต์ น้ำเสียง รูปแบบ การกำหนดเส้นทางข้อจำกัด การจัดรูปแบบ การตรวจสอบไฟล์เฉพาะขั้นตอนซับซ้อนหลายขั้นที่มีพารามิเตอร์
ทำงานเมื่อใดทุกข้อความของโมเดลในเซสชันเมื่อเกิด event ในวงจรการทำงานเมื่อโมเดลเรียกใช้
บล็อกการกระทำได้หรือไม่ไม่ได้ได้ (exit 2)ไม่ได้

เทมเพลต CLAUDE.md ฉบับเต็มและกฎ 6 ข้อสำหรับไฟล์ที่ดูแลต่อเนื่องอยู่ในคู่มือ วิธีกำหนดค่า CLAUDE.md ในปี 2026: เทมเพลตพร้อมใช้และกฎ 6 ข้อ ดูข้อมูลเกี่ยวกับ skills ในฐานะความสามารถที่มีชื่อได้ที่ Claude Code Skills: วิธีสร้างคลังของคุณเอง

Hook เป็นระดับต่ำสุด: ไม่มีโมเดล ไม่มีการตีความ มีแค่ shell

Hooks เป็นหนึ่งในเครื่องมือด้าน context engineering ควบคู่กับ Plan Mode และ /compact

Hook 4 ประเภท: Command, HTTP, Prompt, Agent — ควรเลือกแบบไหน?

ประเด็นสำคัญ

Claude Code มี handler 4 ประเภท Command คือสคริปต์ shell และเป็นประเภทที่พบบ่อยที่สุด HTTP ส่งคำขอ POST ไปยัง URL ของคุณ เหมาะสำหรับทีมและการผสานระบบ Prompt เรียก Haiku สั้น ๆ เพื่อตรวจสอบความหมาย Agent สร้าง subagent ที่เข้าถึงเครื่องมือได้เพื่อการตรวจสอบเชิงลึก มือใหม่ต้องใช้ Command และ Prompt ซึ่งครอบคลุมงาน 90%

ประเภททำอะไรควรเลือกใช้เมื่อใดความซับซ้อน
Commandเรียกคำสั่ง shell รับ JSON ผ่าน stdin และตอบกลับด้วย exit code และ stdout90% ของกรณี: ตรวจสอบ path, จัดรูปแบบ, grep เนื้อหา, lsof, การทำงานกับ gitต่ำ
HTTPส่งคำขอ POST ไปยัง URL ของคุณพร้อม JSON body และรับ JSON ตอบกลับเมื่อกฎเป็นของทีมหรือ CI: การตรวจสอบจากส่วนกลาง แดชบอร์ดสถานะที่ใช้ร่วมกัน การตรวจสอบจากระยะไกลปานกลาง (ต้องมีเซิร์ฟเวอร์)
Promptส่งพรอมป์ต์สั้น ๆ ไปยัง Haiku (ตามค่าเริ่มต้น) แล้วรับ yes/no เพื่อประเมินเชิงความหมายเมื่อ grep ไม่พอ: “ประเมินว่าข้อความนี้ฟังดูเป็นภาษาบริษัทหรือไม่”ต่ำ
Agentสร้าง subagent ที่เข้าถึง Read/Grep/Glob ได้ เพื่อทำการตรวจสอบหลายขั้นตอนการตรวจสอบที่หนักหน่วง: “ค้นหาไฟล์ที่เปลี่ยนทั้งหมดและตรวจว่ามีการทดสอบที่เกี่ยวข้องหรือไม่”สูง

ข้อผิดพลาดที่พบบ่อยที่สุดคือพยายามใช้ Command ทำสิ่งที่ต้องใช้ Prompt ตัวอย่างเช่น “ห้ามใช้ em dash ในไฟล์ .md” เป็นงาน grep จึงเหมาะกับ Command แต่ “ห้ามเขียนด้วยน้ำเสียงแบบบริษัท” เป็นเรื่องเชิงความหมาย จึงต้องใช้ Prompt

หากเพิ่งเริ่มต้น ให้ใช้ Command Hooks พร้อมใช้ทั้ง 7 รายการด้านล่างเป็น Command ทั้งหมด ส่วน HTTP/Prompt/Agent ค่อยใช้เมื่อพร้อม

settings.json อยู่ที่ไหน และมี config 4 ระดับอะไรบ้าง?

ประเด็นสำคัญ

Claude Code ค้นหาการตั้งค่าจาก 4 ตำแหน่ง ได้แก่ ระดับองค์กร (ติดตั้งผ่าน MDM), ระดับผู้ใช้ (~/.claude/settings.json), ระดับโปรเจกต์ (.claude/settings.json ในรูท repository) และระดับ local (.claude/settings.local.json ซึ่งไม่อยู่ใน git) ไฟล์ทั้ง 4 จะถูกรวมกันเมื่อเริ่มเซสชัน โดยระดับที่อยู่ล่างกว่าจะมีสิทธิ์เหนือกว่า

ระดับPathมีผลเมื่อใดอยู่ใน git หรือไม่
องค์กร/Library/Application Support/ClaudeCode/settings.json (Mac), /etc/claude-code/settings.json (Linux)ใช้กับทั้งทีมผ่าน MDMไม่
ผู้ใช้~/.claude/settings.jsonทุกโปรเจกต์ของผู้ใช้คนเดียวไม่
โปรเจกต์.claude/settings.json ในรูท repositoryโปรเจกต์นี้ สำหรับทั้งทีมใช่
Local.claude/settings.local.jsonเฉพาะโปรเจกต์นี้และผู้ใช้คนนี้ไม่ (อยู่ใน .gitignore)

ฉันเก็บ hooks ทั้ง 7 รายการที่อธิบายไว้ด้านล่างใน .claude/settings.json ระดับ โปรเจกต์ จึงถูกรวมโดยอัตโนมัติเมื่อ clone repository และ Claude ใช้กฎเดียวกันในหน้าต่างใหม่ ส่วน hooks ทดลองส่วนตัวจะเก็บไว้ใน .claude/settings.local.json เพื่อไม่ให้รบกวนทีม

ไฟล์ทั้ง 4 จะถูกรวมเมื่อเริ่มเซสชัน หากกฎขัดแย้งกัน ระดับที่อยู่ล่างกว่าจะมีสิทธิ์เหนือกว่า (local เหนือ project และ project เหนือ user)

วิธีสร้าง hook แรกที่เร็วที่สุด คือใช้เมนูโต้ตอบ /hooks ใน Claude Code เลือก event จากรายการ เลือก matcher (เช่น Bash หรือ Edit) แล้ววางคำสั่ง Claude Code จะเขียน JSON ที่ถูกต้องลงใน settings.json ให้

Event ในวงจรการทำงาน 31 รายการ: มือใหม่ควรใช้รายการใดบ้าง?

ประเด็นสำคัญ

ปัจจุบัน Claude Code มี event ตลอดวงจรการทำงาน 31 รายการ แบ่งเป็น 3 ความถี่ ได้แก่ ครั้งเดียวต่อเซสชัน (SessionStart/SessionEnd), ครั้งเดียวต่อเทิร์น (UserPromptSubmit/Stop) และทุกครั้งที่เรียกใช้เครื่องมือ (PreToolUse/PostToolUse) มือใหม่ต้องใช้ 4 event จากทั้งหมด 31 รายการ: PreToolUse สำหรับข้อจำกัด, PostToolUse สำหรับการแก้ไขอัตโนมัติ, SessionStart สำหรับบริบท และ Stop สำหรับการตรวจสอบขั้นสุดท้าย

Event เกิดขึ้นในสามความถี่: ครั้งเดียวต่อเซสชัน (SessionStart, SessionEnd), ครั้งเดียวต่อเทิร์น (UserPromptSubmit, Stop, StopFailure) และทุกครั้งที่เรียกใช้เครื่องมือภายใน agentic loop (PreToolUse, PostToolUse)

— Anthropic, เอกสารอ้างอิง hooks, https://code.claude.com/docs/en/hooks

ควรทำความเข้าใจความถี่ทั้งสามแบบเพื่อไม่ให้สับสน:

  1. ครั้งเดียวต่อเซสชัน - SessionStart, SessionEnd, Setup ทำงานครั้งเดียวเมื่อเปิดและปิด Claude Code ใช้เริ่มต้นบริบทและล้างไฟล์ชั่วคราว
  2. ครั้งเดียวต่อเทิร์น - UserPromptSubmit, UserPromptExpansion, Stop, StopFailure ทำงานเมื่อมีข้อความจากคุณแต่ละครั้งและเมื่อโมเดลหยุดแต่ละครั้ง ใช้บันทึกบทสนทนาและตรวจสอบผลลัพธ์สุดท้าย
  3. ทุกครั้งที่เรียกใช้เครื่องมือ - PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied, PostToolBatch, SubagentStart, SubagentStop ทำงานทุกครั้งที่แก้ไขไฟล์ เปิด Bash อ่าน หรือค้นหา นี่คือชั้นที่ทำงานถี่ที่สุด ซึ่งเป็นที่อยู่ของข้อจำกัด การจัดรูปแบบ และการตรวจสอบ

รายการ event ทั้ง 31 รายการยังรวม event แบบ asynchronous/reactive ด้วย ได้แก่ TeammateIdle, PreCompact, PostCompact, Elicitation, ElicitationResult, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Notification รายการเหล่านี้ใช้กับกรณีขั้นสูง ข้ามไปก่อนได้

แบบทดสอบ 5 คำถาม: กฎควรอยู่ใน hook หรือคงไว้ใน CLAUDE.md?

ประเด็นสำคัญ

ถ้ากฎเกี่ยวกับการกระทำ (ไม่ใช่บริบท) เกี่ยวข้องกับความปลอดภัย/เงิน/ข้อมูล ตรวจสอบได้ด้วย grep/regex แก้ไขผลเสียได้ยาก และ Claude เคยละเมิดกฎนี้อย่างน้อยหนึ่งครั้ง กฎนั้นควรอยู่ใน hook หากคำตอบ “ใช่” น้อยกว่า 3 ข้อจาก 5 ข้อ ให้คงไว้ใน CLAUDE.md ฉันทดสอบกับเนื้อหา 80 บรรทัดของตัวเอง แล้วกฎ 5 ข้อถูกย้ายไป hooks

สำหรับฉัน วิธีนี้ทำงานเหมือนกฎหมาย ไม่ใช่คำแนะนำ ฉันย่อการตัดสินใจระหว่าง CLAUDE.md กับ hook ให้เหลือแบบทดสอบง่าย ๆ 5 ข้อ ถ้าตอบ “ใช่” อย่างน้อย 3 ข้อ กฎนั้นจะไปอยู่ใน hook ถ้าน้อยกว่า 3 ข้อ ก็อยู่ในข้อความต่อไป

แบบทดสอบ “กฎควรอยู่ใน hook เมื่อใด”:

  1. กฎนี้เกี่ยวกับการกระทำมากกว่าบริบทหรือไม่? (“ห้ามเรียก rm -rf” เป็นเรื่องการกระทำ; “ฉันมีโปรเจกต์ TypeScript” เป็นบริบท)
  2. กฎนี้เกี่ยวกับความปลอดภัย เงิน หรือข้อมูลหรือไม่? (“ห้ามแตะ .env”, “ห้ามเผยแพร่คีย์”)
  3. ตรวจสอบได้ด้วย grep, regex หรือ exit code ของคำสั่ง shell หรือไม่? (“หลังแก้ .md ให้ตรวจว่าไม่มี em dash”)
  4. หากละเมิดกฎแล้ว ผลที่ตามมาจะแก้ไขได้ยากหรือไม่? (“force push ไปยัง main” แก้ไขได้ยาก; “ลืมใส่จุดท้ายประโยค” แก้ได้ง่าย)
  5. คุณเคยเห็น Claude ลืมกฎนี้อย่างน้อยหนึ่งครั้งแล้วหรือไม่?

ตัวอย่าง: “ห้ามใช้ em dash ให้ใช้ hyphen เท่านั้น” เกี่ยวกับการกระทำไหม? ใช่ ความปลอดภัยไหม? ไม่ ตรวจด้วย grep ได้ไหม? ได้ แก้ไขยากไหม? ไม่ เคยเห็นว่าละเมิดไหม? เคย 3 จาก 5: ใส่ไว้ใน hook

ตัวอย่างที่ตรงกันข้าม: “ฉันมี vibe coders ไม่ใช่ developers ให้เขียนโดยไม่ใช้คำสแลง” เกี่ยวกับการกระทำไหม? เป็นบริบทมากกว่า ความปลอดภัยไหม? ไม่ ตรวจด้วย regex ได้ไหม? ยากเกินไป แก้ไขยากไหม? ไม่ เคยเห็นว่าละเมิดไหม? เคย 1 จาก 5: คงไว้ใน CLAUDE.md

กฎของฉัน 5 ข้อผ่านแบบทดสอบ จึงย้ายไปไว้ใน hooks ส่วนอีก 75% ยังคงอยู่ในไฟล์ข้อความ

Hooks 7 รายการพร้อมคัดลอกไปใช้

ประเด็นสำคัญ

ด้านล่างคือ hooks พร้อมใช้ 7 รายการสำหรับ .claude/settings.json: บล็อกการแก้ไข .env, เรียก prettier โดยอัตโนมัติ, ป้องกัน force-push ไปยัง main, ตรวจหา em dash, เคลียร์พอร์ตก่อน npm run dev, auto-commit หลังทดสอบสำเร็จ และใส่บริบทล่าสุดเมื่อเริ่มเซสชัน คัดลอกแต่ละรายการลงไฟล์ แล้วเริ่ม Claude Code ใหม่ ก็ใช้งานได้เลย

Hooks ทั้ง 7 รายการเก็บไว้ในไฟล์ .claude/settings.json ไฟล์เดียวที่รูทโปรเจกต์ แต่ละรายการประกอบด้วยประเภท event, ตัวกรองเครื่องมือ (matcher) และคำสั่ง shell exit code 2 ปฏิเสธ PreToolUse ที่ตรงก่อนรัน แต่ใน PostToolUse การกระทำเกิดแล้ว hook ให้ feedback และย้อนการกระทำไม่ได้

สำหรับ event ของ hook ส่วนใหญ่ มีเพียง exit code 2 เท่านั้นที่บล็อกการกระทำ Claude Code ถือว่า exit code 1 เป็นข้อผิดพลาดที่ไม่บล็อก และดำเนินการต่อ แม้ตามธรรมเนียม Unix ค่า 1 จะหมายถึงความล้มเหลวก็ตาม

— Anthropic, เอกสารอ้างอิง hooks, https://code.claude.com/docs/en/hooks

exit 0 หมายถึงสำเร็จ exit 1 หมายถึงเกิดข้อผิดพลาด แต่ Claude ทำต่อ (นี่คือ anti-pattern แรกด้านล่าง) exit 2 มีผลตาม event บล็อก PreToolUse ได้ แต่ PostToolUse รายงาน feedback ของสิ่งที่ทำแล้วเท่านั้น

Hook 1. กันการแก้ .env โดยตรง (PreToolUse, exit 2)

บล็อกความพยายามแก้ไขโดยตรงกับไฟล์ใดก็ตามที่ตรงกับ .env, .env.local, .env.production และอื่น ๆ

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Do not edit `.env` directly. Tell me which variable to add, and I’ll open the file myself.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

การทำงาน: ก่อน Edit/Write/MultiEdit ทุกครั้ง hook จะดึง path ของไฟล์ ตรวจด้วย grep ว่าตรงกับรูปแบบ .env หรือไม่ และถ้าตรงก็ส่ง exit 2 พร้อมข้อความ Claude เห็น stderr และบอกฉันว่า “ฉันพยายามแก้ .env แต่ hook บล็อกไว้ บอกฉันว่าต้องเพิ่มตัวแปรใด”

Hook 2. เรียก prettier โดยอัตโนมัติหลังแก้โค้ดทุกครั้ง (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
          }
        ]
      }
    ]
  }
}

การทำงาน: หลัง Edit/Write ทุกครั้ง hook จะดึง path และถ้านามสกุลเป็น .ts/.tsx/.js/.jsx/.json ก็เรียก prettier ทำงานเบื้องหลังด้วย & โดยจะจบด้วย exit 0 เสมอ เพราะเป็นการจัดรูปแบบ ไม่ใช่ข้อจำกัด หาก prettier ล้มเหลวก็ไม่เป็นไร ไฟล์ยังคงสภาพเดิม

ความแตกต่างระหว่าง hook 1 และ hook 2 เป็นเรื่องพื้นฐาน: PreToolUse + exit 2 คือการบล็อกอย่างเด็ดขาด ก่อน ทำงาน ส่วน PostToolUse + exit 0 คือการแก้ไขแบบไม่เข้มงวด หลัง ทำงาน ทั้งสองแบบมีไว้สำหรับงานคนละประเภท

Hook 3. บล็อก git push --force ไปยัง branch main (PreToolUse, exit 2)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push to main is forbidden. Create a new branch or ask me to do it manually.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

การทำงาน: ทุกครั้งที่เปิด Bash จะตรวจว่าคำสั่งมี push, มี --force/--force-with-lease/-f และกล่าวถึง main หรือไม่ หากตรงครบทั้งสามเงื่อนไข ก็จะบล็อกพร้อมข้อความที่ชัดเจน

เมื่อก่อนฉันเขียนกฎนี้ด้วยตัวพิมพ์ใหญ่ทั้งหมดไว้ใน CLAUDE.md Claude ก็ยังละเมิดประมาณทุกสองสัปดาห์ ตอนที่ฉันขอให้ “force push เพื่อยกเลิก commit ที่ทำพลาด” hook จับรูปคำสั่งตรงที่แสดง แต่ aliases สคริปต์ branch ปลายทางโดยนัย หรือ Git client อื่นข้าม string check ได้ ต้องบังคับป้องกัน main บน remote และจำกัด credentials ของเอเจนต์

Hook 4. บล็อก em dash ในไฟล์ข้อความ (PostToolUse, exit 2)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"File $FILE contains an em dash (U+2014). Replace it with a regular hyphen (U+002D).\" >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

การทำงาน: PostToolUse ที่คืน exit 2 รายงานการตรวจหลังแก้ไม่ผ่านแก่ Claude ไม่ย้อนหรือป้องกันการเขียนที่เกิดแล้ว หาก Claude แก้ไฟล์แล้วทิ้ง em dash ไว้ hook จะไม่ผ่าน Claude ได้รับผลตอบกลับและลองอีกครั้ง หลังผ่านไป 2–3 เซสชัน Claude จะเลิกใส่อักขระนี้โดยสิ้นเชิง

โปรดสังเกตว่า grep ค้นหาอักขระ em dash (U+2014) โดยเฉพาะผ่าน $'\xe2\x80\x94' ซึ่งเป็นสัญกรณ์ Bash สำหรับเขียนอักขระนี้เป็นไบต์ UTF-8 หากเขียน grep -q '-' เฉย ๆ ก็จะจับ hyphen ปกติด้วยและทำงานกับทุกไฟล์ อักขระที่มองไม่เห็นทำให้ตรรกะทั้งหมดเสียหาย

Hook 5. เคลียร์พอร์ต 3000 ก่อน npm run dev (PreToolUse, exit 0)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 freed before launch' >&2; }; exit 0"
          }
        ]
      }
    ]
  }
}

การทำงาน: ก่อน npm run dev (หรือ pnpm/yarn) ทุกครั้ง จะฆ่ากระบวนการที่ใช้พอร์ต 3000 และจบด้วย exit 0 เสมอ เพราะเป็นการเตรียมการ ไม่ใช่ข้อจำกัด วิธีนี้ช่วยป้องกันปัญหาที่ Claude เห็นว่า “พอร์ตถูกใช้งานอยู่” แล้วคิดว่าโค้ดมีปัญหาจึงเริ่มแก้ ทั้งที่จริงเซิร์ฟเวอร์เก่ายังค้างอยู่

Hook 6. auto-commit หลังการทดสอบสำเร็จ (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
          }
        ]
      }
    ]
  }
}

การทำงาน: อ่าน JSON จาก stdin ครั้งเดียว รับเฉพาะคำสั่ง test ธรรมดาที่ระบุตรง ทำต่อเฉพาะ tool_response.exit_code มีค่าเป็นศูนย์ชัด หากรุ่นติดตั้งไม่ส่งฟิลด์นี้ให้ข้าม commit แทนเดาว่าสำเร็จ แทน src/orders.ts กับ tests/orders.test.ts ด้วยไฟล์ตรงที่งานรับผิดชอบ หากมี staged changes เดิมจะหยุดสูตรไม่รวมงานอื่น add ล้มต้องหยุด diff ว่างไม่สร้าง commit และยังเปิด commit hooks ปกติ ทดสอบ input สำเร็จ ล้มเหลว และไม่มีผลแบบ offline ก่อนเปิด

รูปแบบที่ควรหลีกเลี่ยงคือเพิ่ม git commit --no-verify ทุก Edit ต้องคง commit checks หาก check เรียกอัตโนมัตินี้ซ้ำวน ให้จำกัด trigger หรือออกแบบ check ใหม่ ไม่ข้ามการตรวจเป็นค่าเริ่มต้น ตรวจ staged diff กับขอบเขตงานก่อนเปิด auto-commit

Hook 7. ใส่บริบทล่าสุดเมื่อเริ่มเซสชัน (SessionStart พร้อม additionalContext)

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
          }
        ]
      }
    ]
  }
}

การทำงาน: เมื่อเริ่มเซสชัน จะรวบรวม branch Git ปัจจุบันและรายการการเปลี่ยนแปลงที่ยังไม่ได้ commit จากนั้นส่ง JSON ที่มีฟิลด์ additionalContext Claude Code จะใส่ข้อความนี้ใน system message แรก ทำให้ Claude รู้ตั้งแต่ต้นว่ากำลังอยู่ที่ไหนและมีงานอะไรจากเซสชันก่อนค้างอยู่

ใช้ additionalContext สำหรับข้อมูลที่ Claude ควรรู้เกี่ยวกับสถานะปัจจุบันของสภาพแวดล้อมหรือการดำเนินการที่เพิ่งเสร็จสิ้น: สถานะสภาพแวดล้อม (branch ปัจจุบัน เป้าหมายการ deploy feature flag ที่ใช้งานอยู่), กฎโปรเจกต์แบบมีเงื่อนไข (คำสั่งทดสอบใดใช้กับไฟล์ที่เพิ่งแก้), ข้อมูลภายนอก (issue ที่เปิดอยู่และมอบหมายให้คุณ ผล CI ล่าสุด)

— Anthropic, เอกสารอ้างอิง hooks, https://code.claude.com/docs/en/hooks

ส่วนขยายของ hook นี้: ดึง commit ล่าสุดและคำอธิบาย สถานะ GitHub Actions sprint ที่กำลังทำใน Linear/Jira และ issue ที่เปิดอยู่ของ repository ปัจจุบัน

ข้อผิดพลาดจาก hooks 3 ข้อที่คนส่วนใหญ่เจอ

ประเด็นสำคัญ

ข้อผิดพลาดแรก: hook ที่ออกด้วย exit 1 ผ่านไปอย่างเงียบ ๆ และ Claude เพิกเฉยต่อข้อผิดพลาด ข้อที่สอง: งานหนักใน hook จะถูกยุติเมื่อครบ timeout 60 วินาที ข้อที่สาม: hook เห็นเฉพาะ environment variable จาก ~/.zshrc ไม่ใช่ .env ของโปรเจกต์ แก้แต่ละข้อได้ด้วยบรรทัดเดียว แต่ถ้าไม่รู้ข้อผิดพลาดเหล่านี้ อาจดูเหมือนว่า “hooks ใช้ไม่ได้”

ข้อผิดพลาด 1. Hook ที่ออกด้วย exit 1 ผ่านไปอย่างเงียบ ๆ

ข้อนี้อันตรายที่สุด เป็นเวลาหนึ่งสัปดาห์ที่ฉันคิดว่าตัวป้องกัน .env ทำงาน แต่ปรากฏว่าใน hook ฉันใช้ jq -r '.tool_input.file_path' โดยไม่ได้ตรวจ null เมื่อ tool_input ว่าง jq ออกด้วย code 1 Claude มอง exit 1 ว่า “hook error, ทำต่อ” การกระทำจึงเกิดขึ้น ฉันไม่เข้าใจว่าทำไมยังมีการแตะ .env จนกระทั่งรัน hook ด้วยตนเองใน terminal โดยป้อน input ว่างและพบสาเหตุ

วิธีแก้มีเพียงกฎเดียว: เริ่ม hook ทุกตัวด้วยการตรวจ null และจบด้วย || exit 0 หากเกิด exit ที่ไม่คาดคิด ก็จะออกอย่าง “ปลอดภัย” แทนที่จะผ่านไปอย่างเงียบ ๆ

# Safe start and end template:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... your logic ...
exit 0   # or || exit 0 on one line

ข้อผิดพลาด 2. Hooks ที่ช้าจะถูกยุติเมื่อหมดเวลา

ฉันเคยมี hook ที่รัน eslint --fix กับทั้งไฟล์หลัง Edit ไฟล์ขนาดใหญ่ (1000+ บรรทัด) ใช้เวลาประมวลผล 8–10 วินาที ค่าเริ่มต้น timeout ของ hook ใน Claude Code คือ 60 วินาที ฉันจึงคิดว่ายังอยู่ในขีดจำกัด แต่เมื่อมีการ Edit หลายครั้งภายในหนึ่งนาที hooks จะสะสมและรอในคิว บางรายการก็ถูกข้ามไป ฉันรู้ตัวเมื่อ log แสดงข้อความ “hook timed out”

วิธีแก้: หาก hook ต้องทำงานหนัก ให้ทำงานเบื้องหลัง eslint --fix "$FILE" & โดยไม่รอผลลัพธ์ จากนั้น hook จะคืน exit 0 ภายในไม่กี่มิลลิวินาที ขณะที่งานจริงยังทำงานอยู่เบื้องหลัง

# Heavy operation in background:
npx eslint --fix "$FILE" &
exit 0

ข้อผิดพลาด 3. Hook มองไม่เห็น environment variable ของโปรเจกต์

ฉันคิดว่า process.env จะใช้ได้ใน hook เช่นเดียวกับแอป Node.js แต่ไม่ใช่ Hook ทำงานใน shell ที่กระบวนการหลักของ Claude Code เรียกขึ้นมา และมองเห็นเฉพาะสิ่งที่อยู่ใน ~/.zshrc หรือ ~/.bashrc รวมถึงสิ่งที่ Claude Code ส่งผ่านให้อย่างชัดเจน หาก hook ของคุณต้องใช้ OPENAI_API_KEY จาก .env คุณต้องอ่าน .env ด้วยตนเอง

วิธีแก้: อ่าน .env อย่างชัดเจนในคำสั่ง hook

# Passing project env into hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Or with dotenv:
dotenv -e .env -- node my-validator.js

ปัญหานี้ลำบากเป็นพิเศษใน monorepo ซึ่งแต่ละแพ็กเกจมีไฟล์ .env ของตัวเอง แก้โดยส่ง PWD ผ่านมาและเรียก cd "$PWD" อย่างชัดเจนตั้งแต่ต้นคำสั่ง

ทดสอบ hooks ก่อน commit อย่างไร?

ประเด็นสำคัญ

Hook เป็นคำสั่ง shell คุณเรียกใช้ด้วยตนเองใน terminal โดยป้อน stdin จำลอง ตรวจ exit code และ stderr ได้ก่อนที่ Claude Code จะเรียกใช้ วิธีนี้ช่วยประหยัดเวลาหลายชั่วโมงในการแก้ปัญหา และป้องกัน hooks ที่บล็อกการทำงานของตัวเอง

สถานการณ์ทดสอบพื้นฐาน นำคำสั่ง hook จาก JSON เปิด terminal แล้วป้อน JSON ที่ Claude Code จะส่งให้:

# Test hook 1 (protect .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
  echo "BLOCKED" >&2
  exit 2
} || exit 0
'
echo "Exit code: $?"

ควรเห็น BLOCKED ใน stderr และ Exit code: 2 หากได้ exit 0 แสดงว่า regex ใช้ไม่ได้ ให้แก้ grep

สถานการณ์ที่ควรตรวจสอบ:

  1. กรณีปกติ - ไฟล์ควรถูกบล็อก: .env สำหรับ hook 1, git push --force main สำหรับ hook 3
  2. กรณีเชิงลบ - ไฟล์ไม่ควรถูกบล็อก: README.md สำหรับ hook 1, git push origin feature-branch สำหรับ hook 3
  3. กรณีขอบ - input ว่าง ค่า null อักขระที่ไม่คาดคิด hook ควรคืน exit 0 หรือไม่ก็ล้มเหลวพร้อมข้อผิดพลาดที่ชัดเจน ห้ามผ่านไปอย่างเงียบ ๆ

ตำแหน่งเก็บ hooks ทดลอง อย่าใส่ใน .claude/settings.json ให้ใส่ใน .claude/settings.local.json (ไม่อยู่ใน git) วาง hooks ใหม่ไว้ใต้ PostToolUse โดยใช้ echo แทนการกระทำจริง ตรวจว่าทำงานหรือไม่ แล้วค่อยย้ายไปยัง settings.json สำหรับใช้งานจริง

// Experiment in settings.local.json - writes to /tmp/hook-debug.log:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
          }
        ]
      }
    ]
  }
}

หลังแก้ไข 5–10 ครั้งใน Claude Code ให้เปิด /tmp/hook-debug.log เพื่อดูว่าไฟล์ใดเรียก hook หากไฟล์ว่าง แสดงว่า matcher ไม่ตรง ให้แก้ regex

ต่อไปทำอะไร?

ประเด็นสำคัญ

เริ่มจาก hooks ของ PostToolUse ที่ออกด้วย exit 0 ซึ่งปลอดภัยที่สุดและไม่บล็อกอะไร หลังผ่านไปหนึ่งสัปดาห์ ให้เพิ่ม hook PreToolUse ตัวแรกที่ใช้ exit 2 เพื่อป้องกัน .env หรือป้องกัน force-push หลังผ่านไปสองสัปดาห์ CLAUDE.md จะลดจากข้อตกลง 200 บรรทัดเหลือคำอธิบายบริบทสั้น ๆ 50–80 บรรทัด โดยย้ายการตรวจ event ที่เลือกไป hooks และบังคับขอบเขตสิทธิ์จริงแยกด้วย permissions, sandboxing และ remote protections

5 ขั้นตอนสำหรับสัปดาห์ที่จะถึงนี้:

  1. ค้นหาใน CLAUDE.md ด้วยคำอย่าง “never,” “forbidden,” “always” และ “must” กฎเหล่านี้เป็นตัวเลือกสำหรับ hook หากเป็นคำสั่ง ไฟล์ข้อความจะบังคับใช้ไม่ได้
  2. เริ่มด้วย PostToolUse + exit 0 ซึ่งปลอดภัยที่สุด: ทำการเปลี่ยนแปลงแต่ไม่บล็อกอะไร Prettier (hook 2), ปิดเซิร์ฟเวอร์ dev (hook 5), auto-commit หลังทดสอบ (hook 6)
  3. เมื่อมั่นใจแล้วเท่านั้น จึงเพิ่ม PreToolUse + exit 2 ตัวแรก เช่น ป้องกัน .env (hook 1) หรือป้องกัน force-push (hook 3) นี่เป็นการบล็อกแบบเด็ดขาด และข้อผิดพลาดในคำสั่ง grep อาจบล็อกคุณเองได้
  4. ทดสอบ hooks ด้วยตนเองก่อน commit (ดูหัวข้อด้านบน) วิธีนี้ช่วยประหยัดเวลาหลายชั่วโมงในการแก้ปัญหา
  5. เก็บ hooks ทดลองไว้ใน settings.local.json เพิ่ม hooks ใหม่ที่นั่นโดยใช้ echo แทนการกระทำจริง

เมื่อตั้งค่า hooks แล้ว ขั้นตอนถัดไปในชุดคู่มือคือ วิธีย้อนการเปลี่ยนแปลงใน Claude Code เผื่อ hook ปล่อยให้มีสิ่งอันตรายผ่านไป หากต้องการให้ Claude จดจำโครงสร้างโปรเจกต์ระหว่างเซสชัน ให้สร้าง Second Brain ใน Claude Code (โฟลเดอร์ business/ + ฐานความรู้ที่เชื่อมโยงกัน) และหากต้องการตั้งชื่อ skills ของโปรเจกต์เพื่อให้เรียกใช้ซ้ำได้ ดูที่ Claude Code Skills

หากต้องการเข้าถึง neural network ผ่าน Ailoot ลองดู Ailoot.

รายละเอียดเพิ่มเติม

คำถามที่พบบ่อย

ทำไม hook ต้องอ่าน JSON input เพียงครั้งเดียว?

stdin เป็นสตรีม jq ตัวแรกอ่านจนหมดทำให้ตัวถัดไปพบ EOF ต้องเก็บ input ครั้งเดียวแล้วดึง command กับผลจากค่าที่เก็บ หากไม่มีผลหรือไม่ทราบให้ถือว่ายังไม่ยืนยันและข้าม auto-commit

regex hooks ตัวอย่างสร้างขอบเขตความปลอดภัยหรือไม่?

ไม่สร้าง ตรวจเฉพาะ event กับรูปคำสั่งที่รู้จัก shell redirection, aliases, scripts, MCP หรือทางเขียนอื่นอาจข้ามได้ ขีดจำกัดจริงต้องใช้ credentials จำกัด สิทธิ์ไฟล์ sandbox และ branch protection

บทความทั้งหมด