My agentic slop goes here. Not intended for anyone else!
1(** Claude Code Hooks System
2
3 Hooks allow you to intercept and control events in Claude Code sessions,
4 such as tool usage, prompt submission, and session stops.
5
6 {1 Overview}
7
8 Hooks are organized by event type, with each event having:
9 - A typed input structure (accessible via submodules)
10 - A typed output structure for responses
11 - Helper functions for common responses
12
13 {1 Example Usage}
14
15 {[
16 open Eio.Std
17
18 (* Block dangerous bash commands *)
19 let block_rm_rf ~input ~tool_use_id:_ ~context:_ =
20 let hook = Hooks.PreToolUse.of_json input in
21 if Hooks.PreToolUse.tool_name hook = "Bash" then
22 let tool_input = Hooks.PreToolUse.tool_input hook in
23 match Ezjsonm.find tool_input ["command"] with
24 | `String cmd when String.contains cmd "rm -rf" ->
25 let output = Hooks.PreToolUse.deny ~reason:"Dangerous command" () in
26 Hooks.continue
27 ~hook_specific_output:(Hooks.PreToolUse.output_to_json output)
28 ()
29 | _ -> Hooks.continue ()
30 else Hooks.continue ()
31
32 let hooks =
33 Hooks.empty
34 |> Hooks.add Hooks.Pre_tool_use [
35 Hooks.matcher ~pattern:"Bash" [block_rm_rf]
36 ]
37
38 let options = Claude.Options.create ~hooks:(Some hooks) () in
39 let client = Claude.Client.create ~options ~sw ~process_mgr () in
40 ]}
41*)
42
43(** The log source for hooks *)
44val src : Logs.Src.t
45
46(** {1 Hook Events} *)
47
48(** Hook event types *)
49type event =
50 | Pre_tool_use (** Fires before a tool is executed *)
51 | Post_tool_use (** Fires after a tool completes *)
52 | User_prompt_submit (** Fires when user submits a prompt *)
53 | Stop (** Fires when conversation stops *)
54 | Subagent_stop (** Fires when a subagent stops *)
55 | Pre_compact (** Fires before message compaction *)
56
57val event_to_string : event -> string
58val event_of_string : string -> event
59
60(** {1 Context} *)
61
62module Context : sig
63 type t
64 val create : ?signal:unit option -> unit -> t
65end
66
67(** {1 Decisions} *)
68
69type decision =
70 | Continue (** Allow the action to proceed *)
71 | Block (** Block the action *)
72
73(** {1 Generic Hook Result} *)
74
75(** Generic result structure for hooks *)
76type result = {
77 decision: decision option;
78 system_message: string option;
79 hook_specific_output: Ezjsonm.value option;
80}
81
82(** {1 Typed Hook Modules} *)
83
84(** PreToolUse hook - fires before tool execution *)
85module PreToolUse : sig
86 (** Typed input for PreToolUse hooks *)
87 type t
88
89 (** Permission decision for tool usage *)
90 type permission_decision = Allow | Deny | Ask
91
92 (** Typed output for PreToolUse hooks *)
93 type output = {
94 permission_decision: permission_decision option;
95 permission_decision_reason: string option;
96 }
97
98 (** Parse hook input from JSON *)
99 val of_json : Ezjsonm.value -> t
100
101 (** {2 Accessors} *)
102 val session_id : t -> string
103 val transcript_path : t -> string
104 val tool_name : t -> string
105 val tool_input : t -> Ezjsonm.value
106 val raw_json : t -> Ezjsonm.value
107
108 (** {2 Response Builders} *)
109 val allow : ?reason:string -> unit -> output
110 val deny : ?reason:string -> unit -> output
111 val ask : ?reason:string -> unit -> output
112 val continue : unit -> output
113
114 (** Convert output to JSON for hook_specific_output *)
115 val output_to_json : output -> Ezjsonm.value
116end
117
118(** PostToolUse hook - fires after tool execution *)
119module PostToolUse : sig
120 type t
121
122 type output = {
123 decision: decision option;
124 reason: string option;
125 additional_context: string option;
126 }
127
128 val of_json : Ezjsonm.value -> t
129
130 val session_id : t -> string
131 val transcript_path : t -> string
132 val tool_name : t -> string
133 val tool_input : t -> Ezjsonm.value
134 val tool_response : t -> Ezjsonm.value
135 val raw_json : t -> Ezjsonm.value
136
137 val continue : ?additional_context:string -> unit -> output
138 val block : ?reason:string -> ?additional_context:string -> unit -> output
139 val output_to_json : output -> Ezjsonm.value
140end
141
142(** UserPromptSubmit hook - fires when user submits a prompt *)
143module UserPromptSubmit : sig
144 type t
145
146 type output = {
147 decision: decision option;
148 reason: string option;
149 additional_context: string option;
150 }
151
152 val of_json : Ezjsonm.value -> t
153
154 val session_id : t -> string
155 val transcript_path : t -> string
156 val prompt : t -> string
157 val raw_json : t -> Ezjsonm.value
158
159 val continue : ?additional_context:string -> unit -> output
160 val block : ?reason:string -> unit -> output
161 val output_to_json : output -> Ezjsonm.value
162end
163
164(** Stop hook - fires when conversation stops *)
165module Stop : sig
166 type t
167
168 type output = {
169 decision: decision option;
170 reason: string option;
171 }
172
173 val of_json : Ezjsonm.value -> t
174
175 val session_id : t -> string
176 val transcript_path : t -> string
177 val stop_hook_active : t -> bool
178 val raw_json : t -> Ezjsonm.value
179
180 val continue : unit -> output
181 val block : ?reason:string -> unit -> output
182 val output_to_json : output -> Ezjsonm.value
183end
184
185(** SubagentStop hook - fires when a subagent stops *)
186module SubagentStop : sig
187 include module type of Stop
188 val of_json : Ezjsonm.value -> t
189 val raw_json : t -> Ezjsonm.value
190end
191
192(** PreCompact hook - fires before message compaction *)
193module PreCompact : sig
194 type t
195 type output = unit
196
197 val of_json : Ezjsonm.value -> t
198
199 val session_id : t -> string
200 val transcript_path : t -> string
201 val raw_json : t -> Ezjsonm.value
202
203 val continue : unit -> output
204 val output_to_json : output -> Ezjsonm.value
205end
206
207(** {1 Callbacks} *)
208
209(** Generic callback function type.
210
211 Callbacks receive:
212 - [input]: Raw JSON input (parse with [PreToolUse.of_json], etc.)
213 - [tool_use_id]: Optional tool use ID
214 - [context]: Hook context
215
216 And return a generic [result] with optional hook-specific output.
217*)
218type callback =
219 input:Ezjsonm.value ->
220 tool_use_id:string option ->
221 context:Context.t ->
222 result
223
224(** {1 Matchers} *)
225
226(** A matcher configuration *)
227type matcher = {
228 matcher: string option; (** Pattern to match (e.g., "Bash" or "Write|Edit") *)
229 callbacks: callback list; (** Callbacks to invoke on match *)
230}
231
232(** Hook configuration: map from events to matchers *)
233type config = (event * matcher list) list
234
235(** {1 Generic Result Builders} *)
236
237(** [continue ?system_message ?hook_specific_output ()] creates a continue result *)
238val continue : ?system_message:string -> ?hook_specific_output:Ezjsonm.value -> unit -> result
239
240(** [block ?system_message ?hook_specific_output ()] creates a block result *)
241val block : ?system_message:string -> ?hook_specific_output:Ezjsonm.value -> unit -> result
242
243(** {1 Configuration Builders} *)
244
245(** [matcher ?pattern callbacks] creates a matcher *)
246val matcher : ?pattern:string -> callback list -> matcher
247
248(** Empty hooks configuration *)
249val empty : config
250
251(** [add event matchers config] adds matchers for an event *)
252val add : event -> matcher list -> config -> config
253
254(** {1 JSON Serialization} *)
255
256val result_to_json : result -> Ezjsonm.value
257val config_to_protocol_format : config -> Ezjsonm.value