hook events 30 ตัวทำงานในหนึ่งเซสชันของ Claude Code มีแค่ 3 ตัวที่พูดกลับได้

รายการ hook events ทั้งหมดของ Claude Code ว่าแต่ละตัวทำงานตอนไหน ตัวไหนใน 15 ตัวที่บล็อกได้ และกฎของ stdout ที่กลืนเอาต์พุตของ hook ส่วนใหญ่ไปอย่างเงียบ ๆ เอกสารอ้างอิงจากหน้างานจริง ที่สร้างจากการรัน hook ในโปรดักชันข้ามเซสชันเอเจนต์นับพัน

มีอาการพังสองแบบที่โผล่มาซ้ำแล้วซ้ำอีกเวลาคนต่อ hook เข้ากับ Claude Code และทั้งสองแบบไม่เหมือนกันเลย

แบบแรก: คุณเพิ่ม hook เข้าไป แล้วไม่มีอะไรเกิดขึ้น ไม่มี error ไม่มีคำเตือน ไม่มีบรรทัดใน log hook ตัวนั้นไม่เคยทำงานเลย

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

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

มี 30 อีเวนต์ ไม่ใช่หกตัว

คู่มือส่วนใหญ่พูดถึง PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification และ SubagentStop หกตัวนั้นมีอยู่จริง และแบกงานที่มีประโยชน์ไว้เกือบทั้งหมด แต่มันก็เป็นแค่หนึ่งในห้าของทั้งหมดที่มี

รายการทั้งหมด จัดกลุ่มตามสิ่งที่แต่ละตัวเฝ้าดู:

กลุ่มอีเวนต์
เซสชันSessionStart, SessionEnd, Setup
PromptUserPromptSubmit, UserPromptExpansion
เครื่องมือPreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch
สิทธิ์PermissionRequest, PermissionDenied
เทิร์นStop, StopFailure
Subagent และงานSubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle
บริบทPreCompact, PostCompact, InstructionsLoaded
สภาพแวดล้อมFileChanged, CwdChanged, ConfigChange
WorktreeWorktreeCreate, WorktreeRemove
อินเทอร์เฟซNotification, MessageDisplay
MCP elicitationElicitation, ElicitationResult

แผนภาพไทม์ไลน์ของ hook events ทั้ง 30 ตัวใน Claude Code เรียงตามลำดับที่ทำงานระหว่างเซสชันของเอเจนต์ ตั้งแต่ SessionStart ผ่าน UserPromptSubmit, PreToolUse, PostToolUse, Stop ไปจนถึง SessionEnd พร้อมแสดงว่าอีเวนต์ใดบล็อกเอเจนต์ได้บ้าง

ลำดับการทำงานของอีเวนต์ภายในเซสชันเดียว บล็อกของเครื่องมือวนซ้ำหนึ่งรอบต่อการเรียกเครื่องมือหนึ่งครั้ง ส่วนบล็อก prompt ทั้งก้อนวนซ้ำหนึ่งรอบต่อหนึ่งเทิร์น

บางตัวในนี้เปลี่ยนวิธีที่คุณมองระบบทั้งหมด PostToolUseFailure มีอยู่จริง แปลว่าคำถามที่ว่า "เครื่องมือทำงานสำเร็จไหม" เป็นอีเวนต์ ไม่ใช่สิ่งที่ต้องอนุมานเอาจาก payload PostToolBatch ทำงานหนึ่งครั้งหลังจากชุดการเรียกเครื่องมือแบบขนานเสร็จสิ้น ซึ่งเป็นจุดที่เหมาะจะรัน linter ครั้งเดียว แทนที่จะรันทุกครั้งที่มีการแก้ไข InstructionsLoaded ทำงานตอนที่ CLAUDE.md ถูกอ่าน ทำให้คุณมีจุดเกาะไว้ตรวจว่าเอเจนต์โหลดกฎที่คุณคิดว่ามันโหลดจริงหรือเปล่า

กฎของ stdout ที่กลืนเอาต์พุตของ hook ส่วนใหญ่

นี่คือเรื่องที่มีประโยชน์ที่สุดในหน้านี้

เมื่อ exit code เป็น 0 Claude Code จะพาร์ส stdout เพื่อหาฟิลด์เอาต์พุตแบบ JSON แต่ stdout นั้นจะถูกแสดงให้เอเจนต์เห็นหรือไม่ ขึ้นอยู่กับอีเวนต์ และข้อยกเว้นมีอยู่แค่รายการสั้น ๆ จากเอกสารอ้างอิง:

สำหรับอีเวนต์ส่วนใหญ่ stdout จะถูกเขียนลง debug log แต่ไม่แสดงใน transcript ข้อยกเว้นคือ UserPromptSubmit, UserPromptExpansion และ SessionStart ซึ่ง stdout จะถูกเพิ่มเข้าไปเป็นบริบทที่ Claude มองเห็นและนำไปใช้ต่อได้

สามอีเวนต์จากสามสิบตัว ถ้าคุณสั่ง echo "warning: this migration is destructive" จาก hook ของ PostToolUse แล้วหวังว่าเอเจนต์จะอ่าน มันจะไม่มีวันอ่าน ข้อความของคุณไปอยู่ใน debug log แล้ว

มีอยู่แค่สองวิธีเท่านั้นที่จะเอาข้อความไปวางตรงหน้าเอเจนต์จากอีเวนต์อื่น:

  1. ออกด้วยโค้ด 2 แล้วเขียนลง stderr เมื่อออกด้วยโค้ด 2 Claude Code จะไม่สนใจ stdout และ JSON ใด ๆ ที่อยู่ในนั้น แล้วส่ง stderr กลับไปให้เอเจนต์ในฐานะข้อความ error
  2. ออกด้วยโค้ด 0 แล้วพิมพ์อ็อบเจ็กต์ JSON ที่พก hookSpecificOutput.additionalContext มาด้วย

สังเกตความไม่สมมาตรในข้อแรก ออกด้วยโค้ด 0 แปลว่า stdout มีผล ส่วน stderr ไม่มีผล ออกด้วยโค้ด 2 แปลว่า stderr มีผล ส่วน stdout ถูกทิ้งทั้งหมด การเข้าใจสองอย่างนี้สลับกันคือเหตุผลที่ hook ดูถูกต้องทุกประการแต่ยังเงียบสนิท

exit code อื่นทั้งหมดถือเป็น error ที่ไม่บล็อก transcript จะแสดงข้อความแจ้ง <hook name> hook error พร้อมบรรทัดแรกของ stderr การทำงานเดินหน้าต่อ และ stderr ทั้งก้อนไปตกอยู่ใน debug log

แผนภาพ exit code ของ hook ใน Claude Code: ออกด้วยโค้ด 0 จะส่ง JSON จาก stdout ไปให้เอเจนต์เฉพาะสามอีเวนต์เท่านั้น ออกด้วยโค้ด 2 จะบล็อกการกระทำและส่ง stderr ไปให้เอเจนต์ ส่วน exit code อื่นเป็น error ที่ไม่บล็อกและถูกเขียนลง debug log

ช่องทางไหนไปถึงเอเจนต์บ้าง แยกตาม exit code เส้นประคือเส้นทางที่คนคิดว่ามี แต่จริง ๆ ไม่มี

ครึ่งหนึ่งพอดีที่บล็อกได้

สิบห้าอีเวนต์หยุดการกระทำเมื่อออกด้วยโค้ด 2 อีกสิบห้าตัวไม่สนใจแล้วเดินหน้าต่อ

บล็อกได้: PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, Stop, SubagentStop, TeammateIdle, TaskCreated, TaskCompleted, ConfigChange, PostToolBatch, PreCompact, Elicitation, ElicitationResult, WorktreeCreate

บล็อกไม่ได้: PostToolUse, PostToolUseFailure, PermissionDenied, StopFailure, Notification, SubagentStart, SessionStart, Setup, SessionEnd, CwdChanged, FileChanged, PostCompact, WorktreeRemove, InstructionsLoaded, MessageDisplay

ผลในทางปฏิบัติ: guardrail ต้องอยู่บน PreToolUse ไม่ใช่บน PostToolUse เพราะ PostToolUse ทำงานหลังจากเครื่องมือทำสำเร็จไปแล้ว การออกด้วยโค้ด 2 ตรงนั้นไม่ได้ย้อนการเขียนกลับ มันแค่พิมพ์ error ออกมาในขณะที่ความเสียหายนอนอยู่บนดิสก์แล้ว ถ้าคุณอยากหยุด rm -rf คุณมีที่ให้ทำอยู่ที่เดียวเท่านั้น

การที่ PostToolBatch บล็อกได้ ในขณะที่ PostToolUse บล็อกไม่ได้ คุ้มค่าที่จะมองซ้ำอีกรอบ มันแปลว่าการตรวจสอบระดับชุดยังหยุดเทิร์นได้หลังจากการแก้ไขแบบขนานลงดิสก์ไปแล้ว ซึ่งเป็นสิ่งที่ใกล้เคียงกับสิทธิ์ยับยั้งหลังการเขียนมากที่สุดเท่าที่ระบบนี้มีให้

matcher เทียบแบบตรงตัว จนกระทั่งจู่ ๆ ก็ไม่ใช่

ฟิลด์ matcher เปลี่ยนวิธีประเมินตามอักขระที่อยู่ในตัวมันเอง และไม่มีอะไรบอกคุณเลยว่ามันไปเข้าเส้นทางไหน

Matcherถูกประเมินเป็น
"*", "" หรือไม่ระบุตรงกับทุกอย่าง
มีแค่ตัวอักษร ตัวเลข _, -, ช่องว่าง, ,, |สตริงตรงตัว หรือรายการของสตริงตรงตัวที่ตัดแบ่งด้วย | หรือ ,
อย่างอื่นนอกจากนี้นิพจน์ปกติของ JavaScript แบบไม่ยึดหัวท้าย

กับดักอยู่ที่คำว่าไม่ยึดหัวท้าย เอกสารอ้างอิงระบุชัดว่านิพจน์ถูกทดสอบด้วย RegExp.prototype.test ซึ่งผ่านทันทีที่เจอส่วนที่ตรงกันตรงไหนก็ได้ในค่านั้น ดังนั้น Edit.* จึงตรงกับ Edit และ NotebookEdit ถ้าคุณหมายถึงเครื่องมือตัวเดียว ให้เขียน ^Edit$

พฤติกรรมสองอย่างที่ขึ้นกับเวอร์ชัน ควรรู้ไว้ก่อนจะไปไล่ดีบักผิดจุด:

  • จุลภาคในฐานะตัวคั่นและการยอมรับช่องว่าง ต้องใช้ Claude Code v2.1.191 ขึ้นไป
  • ยัติภังค์เพิ่งเข้ามาอยู่ในชุดอักขระของการเทียบแบบตรงตัวใน v2.1.195 ก่อนหน้านั้น matcher อย่าง code-reviewer ถูกมองเป็นนิพจน์ปกติแบบไม่ยึดหัวท้าย จึงทำงานกับ senior-code-reviewer ด้วย

ใน AgentsRoom เราจำกัดขอบเขต hook ที่ใช้ระบุเจ้าของไฟล์ของเราเองด้วย Write|Edit|MultiEdit|NotebookEdit ซึ่งยังอยู่บนเส้นทางสตริงตรงตัว และตรงกับเครื่องมือสี่ตัวนั้นเท่านั้น ไม่มีตัวอื่น ส่วน hook วงจรชีวิตที่เราติดตั้งไม่มี matcher เลย เพราะมันเกี่ยวกับเราเสมอ

หกแห่งที่นิยาม hook ได้ และทั้งหมดจะถูกรวมเข้าด้วยกัน

สัญชาตญาณแรกคือไปหาลำดับความสำคัญ ซึ่งมันไม่มี และนั่นแหละคือส่วนที่น่าสนใจ

ตำแหน่งขอบเขต
~/.claude/settings.jsonทุกโปรเจกต์ของคุณ เฉพาะบนเครื่องของคุณ
.claude/settings.jsonโปรเจกต์เดียว commit ได้
.claude/settings.local.jsonโปรเจกต์เดียว Claude Code gitignore ให้
Managed policy settingsทั้งองค์กร ควบคุมโดยผู้ดูแลระบบ
hooks/hooks.json ของปลั๊กอินตราบใดที่ปลั๊กอินยังเปิดใช้งาน
frontmatter ของ skill หรือ agentตราบใดที่คอมโพเนนต์นั้นยังทำงานอยู่

จากเอกสารอ้างอิง:

รายการ hook จะถูกรวมกันข้ามระดับของ settings แทนที่จะแทนที่กัน: settings ระดับผู้ใช้ ระดับโปรเจกต์ และระดับ local ต่างเพิ่ม hook ของตัวเองเข้ามาโดยไม่ลบ hook ที่ถูกจัดการไว้ และการตั้งค่า disableAllHooks ก็ปิด hook ที่ถูกจัดการไว้จากภายนอก managed settings ไม่ได้

ดังนั้น hook ระดับโปรเจกต์ไม่เคยแทนที่ hook ระดับ global แต่ซ้อนทับขึ้นไปแทน หกแหล่ง บวกกันทั้งหมด ตัวจัดรูปแบบโค้ดบน PostToolUse ที่นิยามไว้ทั้งใน settings ระดับผู้ใช้และในโปรเจกต์อีกรอบ จะรันสองครั้งต่อการแก้ไขหนึ่งครั้ง และอาการเดียวที่เห็นคือรู้สึกว่าทุกอย่างมันช้า

แผนภาพแสดงตำแหน่ง settings ทั้งหกแห่งของ Claude Code ที่นิยาม hook ได้ ทั้งหมดถูกรวมแบบบวกกันเข้าเป็นชุด hook ชุดเดียว แทนที่จะแทนที่กันเอง

หกแหล่ง หนึ่งชุดที่รวมกันแล้ว ไม่มีอะไรในนี้แทนที่อะไรเลย

เรื่องนี้ยังอธิบายด้วยว่าทำไม .claude/settings.local.json ถึงเป็นที่ที่ถูกต้องสำหรับเครื่องมือที่จะติดตั้ง hook ลงในโปรเจกต์ของคนอื่น มันจำกัดขอบเขตอยู่แค่โปรเจกต์นั้น Claude Code gitignore ให้อยู่แล้ว และมันถูกโหลดโดยไม่ต้องใส่ flag ใด ๆ ใน CLI นั่นคือที่ที่ AgentsRoom เขียนรายการของมันลงไป เพื่อให้ .claude/settings.json ที่ผู้ใช้ commit ไว้ไม่ถูกแตะต้อง และเพื่อนร่วมทีมของเขาจะไม่รับ path ที่ผูกกับเครื่องเฉพาะเครื่องติดไปด้วย

สิ่งที่การรัน hook ในโปรดักชันสอนเรา

AgentsRoom ติดตั้ง hook ลงในทุกโปรเจกต์ที่มันเปิด เพื่อติดตามสถานะของเอเจนต์แบบกำหนดผลได้แน่นอน และเพื่อระบุว่าไฟล์ที่ถูกแก้ไขเป็นของเอเจนต์ตัวไหน มีบางอย่างที่โผล่มาให้เห็นเฉพาะตอนทำงานในสเกลนั้น

ชื่ออีเวนต์ที่ไม่รู้จักถูกข้ามไปอย่างเงียบ ๆ เรื่องนี้ไม่ได้อยู่ในเอกสาร และเราพึ่งพามัน เวลาเราเพิ่มอีเวนต์วงจรชีวิตตัวใหม่เข้าไปในตัวติดตั้งของเรา ผู้ใช้ที่ยังอยู่บน CLI เวอร์ชันเก่าจะได้ settings.local.json ที่มีชื่ออีเวนต์ซึ่งไบนารีของเขาไม่เคยรู้จัก ไม่มีอะไรพัง ไม่มีอะไรเตือน รายการนั้นถูกข้ามไปเฉย ๆ นั่นคือสิ่งที่ทำให้ปล่อยตัวติดตั้งออกไปก่อน CLI เวอร์ชันใหม่ได้อย่างปลอดภัย และแน่นอนว่านั่นก็เป็นเหตุผลเดียวกับที่การพิมพ์ผิดหนึ่งตัวให้ผลเป็นความเงียบสนิท แทนที่จะเป็น error

agent_id คือสิ่งที่บอกว่าคุณอยู่ข้างใน subagent ฟิลด์นี้จะมีก็ต่อเมื่อ hook ทำงานอยู่ภายในการเรียก subagent เท่านั้น เรื่องนี้สำคัญกว่าที่ฟังดู Stop ทำงานตอนที่ subagent จบเทิร์นของมัน ไม่ใช่แค่เอเจนต์หลัก กฎง่าย ๆ แบบ "ทำเครื่องหมายว่าเซสชันจบเมื่อเจอ Stop" จะทำเครื่องหมายว่าทั้งเซสชันจบตั้งแต่ subagent ตัวแรกกลับมา เราข้ามอีเวนต์จบเทิร์นที่พก agent_id มาด้วยก็เพราะเหตุนี้เป๊ะ ๆ

อย่าอ่าน transcript_path สำหรับเทิร์นปัจจุบัน เอกสารอ้างอิงเตือนว่า transcript ถูกเขียนแบบอะซิงโครนัสและอาจตามหลังบทสนทนาที่อยู่ในหน่วยความจำ ข้อความล่าสุดจึงอาจยังไม่อยู่ในนั้นตอนที่ hook ของคุณทำงาน Stop และ SubagentStop ได้รับ last_assistant_message มาด้วยก็เพื่อให้คุณไม่ต้องไปแข่งกับไฟล์นั้นเลย

hook คือสัญญาณสถานะเดียวที่เชื่อถือได้ ก่อนจะมี hook เราขูดข้อมูลจาก PTY เพื่อเดาว่าเอเจนต์กำลังคิด กำลังรอ หรือทำเสร็จแล้ว วิธีนั้นพังทันทีที่ CLI เรนเดอร์ผ่าน alternate screen buffer ของเทอร์มินัล ซึ่งเป็นสิ่งที่ /tui fullscreen ทำ ส่วน hook ทำงานเหมือนกันเป๊ะไม่ว่าจะเรนเดอร์ด้วยวิธีไหน ถ้าคุณกำลังสร้างอะไรก็ตามที่เฝ้าดูเอเจนต์จากภายนอก นี่คือเลเยอร์ที่ควรสร้างทับ ส่วนการขูดหน้าจอเก็บไว้เป็นแผนสำรองอย่างมากที่สุด

async: true แทบไม่มีต้นทุน คำสั่งของ hook ประกาศ async: true ได้ แล้วเอเจนต์จะไม่รอมัน hook ของเรายิง POST ไปยัง endpoint ในเครื่องด้วยเพดาน 2 วินาทีแล้วคืนค่าทันที ความหน่วงของเทิร์นเอเจนต์จึงไม่ถูกกระทบ แม้ตอนที่แอปฝั่งรับปิดอยู่ ถ้า hook ของคุณแค่เฝ้าดูและไม่เคยตัดสินใจอะไร ทำให้มันเป็น async แล้วเลิกจ่ายค่าใช้จ่ายนั้นไปเลย

อย่าปล่อยให้ hook เขียนขยะลงเทอร์มินัล สคริปต์ของเรากลืนทุก exception ไว้ รวมถึงที่ระดับบนสุด traceback ของ Python ที่ไม่ถูกจับไม่ได้แค่ล้มเหลวอย่างเงียบ ๆ แต่มันพิมพ์ stack trace ลงในเซสชันเทอร์มินัลของผู้ใช้ กลางวงงานของเขาเลย

timeout

ค่าเริ่มต้นให้มาเผื่อไว้เยอะ ยกเว้นสามกรณีที่ไม่ใช่แบบนั้น:

ประเภทของ hooktimeout ค่าเริ่มต้น
command, http, mcp_tool600 วินาที
prompt30 วินาที
agent60 วินาที
UserPromptSubmit (command, http, mcp_tool)30 วินาที
MessageDisplay (command, http, mcp_tool)10 วินาที
SessionEnd1.5 วินาที แชร์ร่วมกันทุก hook และจะถูกขยับขึ้นให้เท่ากับค่า timeout ต่อ hook ที่ยาวกว่า ได้สูงสุด 60 วินาที

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

ฉบับย่อ

  • มี 30 อีเวนต์ หกตัวที่คนรู้จัก
  • stdout ไปถึงเอเจนต์เฉพาะบน UserPromptSubmit, UserPromptExpansion และ SessionStart เท่านั้น ที่อื่นทั้งหมดให้ออกด้วยโค้ด 2 พร้อม stderr หรือใช้ additionalContext ใน JSON
  • 15 อีเวนต์บล็อกเมื่อออกด้วยโค้ด 2 อีก 15 ตัวไม่สนใจ guardrail ต้องอยู่บน PreToolUse
  • matcher เป็นสตริงตรงตัว จนกว่าอักขระพิเศษตัวหนึ่งจะเปลี่ยนมันเป็นนิพจน์ปกติแบบไม่ยึดหัวท้าย
  • แหล่ง settings หกแห่งรวมกันแบบบวก ไม่มีอะไรแทนที่อะไรเลย
  • ชื่ออีเวนต์ที่สะกดผิดล้มเหลวแบบเงียบสนิท

ถ้าคุณอยากเห็นอีเวนต์เหล่านี้ทำงานจริง มากกว่าจะนั่งคิดเอาเอง นั่นคือสิ่งที่เราสร้างขึ้น: AgentsRoom แสดงทุกการทริกเกอร์ของ hook แยกตามเอเจนต์ ตามโปรเจกต์ ตามรอบการรัน ครอบคลุมทั้ง เอเจนต์ที่รันขนานกันหลายสิบตัว และ เซสชันของ subagent ส่วน hook ที่คุณตั้งค่าไว้ใน settings ของคุณเองยังทำงานตรงตามที่เขียนไว้ทุกประการ เพราะมันรัน CLI ตัวจริง

อ่านต่อ

ดาวน์โหลด AgentsRoom

เรียกใช้ AI agents ของคุณ (Claude, Codex, Antigravity CLI, OpenCode, Aider, Grok Build, Mistral Vibe, Kimi Code) ในทุกโปรเจกต์ของคุณจากหน้าต่างเดียว

ฟรีดาวน์โหลด AgentsRoom

แอปคู่หู: ตรวจสอบตัวแทนของคุณได้ทุกที่

นำของคุณเอง: Claude, Codex, Antigravity CLI, หรือผู้ให้บริการ AI อื่น ๆ

รับส่วนขยาย
Chrome Web Store

ส่งข้อบกพร่องและคำขอไปยังแบ็คล็อกสาธารณะของคุณโดยตรง

มองเห็น AgentsRoom ในการทำงาน

หลายโปรเจกต์
ผู้ให้บริการหลายราย
หลาย agents
สถานะสด
ไฟล์ diff & commit
คู่หูมือถือ
ตัวอย่างสด
ทีมตัวแทน
การทำงานอัตโนมัติในเบราว์เซอร์
การพัฒนาที่ขับเคลื่อนด้วย backlog
ห้องสมุดคำสั่ง
ห้องสมุดทักษะ
ดูฟีเจอร์ทั้งหมด