What the mod and the daemon both have to agree on: the hook events, what is done with each, and the messages that cross between them. The mod compiles this to JavaScript and the daemon natively, so the two cannot disagree about a name or a field.
6use serde::{Deserialize, Serialize};
Every Claude Code settings-hook event (the classic.<Event> names a mod
hooks), as of Claude Code 2.1.287.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 11pub enum HookEvent { 12 PreToolUse, 13 PostToolUse, 14 PostToolUseFailure, 15 PostToolBatch, 16 PermissionDenied, 17 Notification, 18 UserPromptSubmit, 19 UserPromptExpansion, 20 SessionStart, 21 SessionEnd, 22 Stop, 23 StopFailure, 24 SubagentStart, 25 SubagentStop, 26 PreCompact, 27 PostCompact, 28 PreModelSwitch, 29 PostModelSwitch, 30 PermissionRequest, 31 Setup, 32 TeammateIdle, 33 TaskCreated, 34 TaskCompleted, 35 Elicitation, 36 ElicitationResult, 37 ConfigChange, 38 InstructionsLoaded, 39 WorktreeCreate, 40 WorktreeRemove, 41 CwdChanged, 42 FileChanged, 43 DirectoryAdded, 44 MessageDisplay, 45}
47impl HookEvent { 48 pub const ALL: [HookEvent; 33] = [ 49 Self::PreToolUse, 50 Self::PostToolUse, 51 Self::PostToolUseFailure, 52 Self::PostToolBatch, 53 Self::PermissionDenied, 54 Self::Notification, 55 Self::UserPromptSubmit, 56 Self::UserPromptExpansion, 57 Self::SessionStart, 58 Self::SessionEnd, 59 Self::Stop, 60 Self::StopFailure, 61 Self::SubagentStart, 62 Self::SubagentStop, 63 Self::PreCompact, 64 Self::PostCompact, 65 Self::PreModelSwitch, 66 Self::PostModelSwitch, 67 Self::PermissionRequest, 68 Self::Setup, 69 Self::TeammateIdle, 70 Self::TaskCreated, 71 Self::TaskCompleted, 72 Self::Elicitation, 73 Self::ElicitationResult, 74 Self::ConfigChange, 75 Self::InstructionsLoaded, 76 Self::WorktreeCreate, 77 Self::WorktreeRemove, 78 Self::CwdChanged, 79 Self::FileChanged, 80 Self::DirectoryAdded, 81 Self::MessageDisplay, 82 ];
What is done with this event. Exhaustive, so a new event cannot be added without deciding.
86 pub fn role(self) -> Role { 87 match self { 88 // Jev is asked and the answer changes what happens. 89 Self::PreToolUse | Self::Stop => Role::Decide, 90 // The daemon needs these to do the deciding: the prompt a Stop is 91 // judged against, what became of a command it judged, and the end 92 // of a session so its state is dropped. 93 Self::UserPromptSubmit 94 | Self::PostToolUse 95 | Self::PostToolUseFailure 96 | Self::PermissionDenied 97 | Self::SessionEnd => Role::Observe, 98 // Nothing here is judged or needed for a judgment yet. 99 Self::PostToolBatch 100 | Self::Notification 101 | Self::UserPromptExpansion 102 | Self::SessionStart 103 | Self::StopFailure 104 | Self::SubagentStart 105 | Self::SubagentStop 106 | Self::PreCompact 107 | Self::PostCompact 108 | Self::PreModelSwitch 109 | Self::PostModelSwitch 110 | Self::PermissionRequest 111 | Self::Setup 112 | Self::TeammateIdle 113 | Self::TaskCreated 114 | Self::TaskCompleted 115 | Self::Elicitation 116 | Self::ElicitationResult 117 | Self::ConfigChange 118 | Self::InstructionsLoaded 119 | Self::WorktreeCreate 120 | Self::WorktreeRemove 121 | Self::CwdChanged 122 | Self::FileChanged 123 | Self::DirectoryAdded 124 | Self::MessageDisplay => Role::Ignore, 125 } 126 } 127}
What is done with an event.
Sent to the daemon and waited on: the answer changes what happens.
134 Decide,
Sent to the daemon without waiting.
136 Observe,
What the daemon decided about a deciding event.
Run it without asking the user.
146 Allow,
Ask the user first (a tool call), or do not stop yet (a Stop).
148 Ask,
The daemon's answer to a deciding event.
One line for the user: what Jev said the thing was and how sure it was, or why nothing was asked.
159 pub line: String,
How long the daemon took, Jev included.
161 pub ms: u64,
166#[cfg(test)] 167mod tests { 168 use super::*; 169 170 #[test] 171 fn every_event_is_listed_once() { 172 let mut seen = std::collections::HashSet::new(); 173 for event in HookEvent::ALL { 174 assert!(seen.insert(event), "{event:?} twice"); 175 } 176 assert_eq!(seen.len(), 33); 177 } 178 179 #[test] 180 fn names_are_the_hook_names() { 181 assert_eq!(serde_json::to_string(&HookEvent::PreToolUse).unwrap(), "\"PreToolUse\""); 182 assert_eq!(serde_json::from_str::<HookEvent>("\"Stop\"").unwrap(), HookEvent::Stop); 183 } 184}