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 get_string json key =
20 match json with
21 | Jsont.Object (members, _) ->
22 List.find_map (fun ((name, _), value) ->
23 if name = key then
24 match value with
25 | Jsont.String (s, _) -> Some s
26 | _ -> None
27 else None
28 ) members
29 | _ -> None
30 in
31 let block_rm_rf ~input ~tool_use_id:_ ~context:_ =
32 let hook = Hooks.PreToolUse.of_json input in
33 if Hooks.PreToolUse.tool_name hook = "Bash" then
34 let tool_input = Hooks.PreToolUse.tool_input hook in
35 match get_string tool_input "command" with
36 | Some cmd when String.contains cmd "rm -rf" ->
37 let output = Hooks.PreToolUse.deny ~reason:"Dangerous command" () in
38 Hooks.continue
39 ~hook_specific_output:(Hooks.PreToolUse.output_to_json output)
40 ()
41 | _ -> Hooks.continue ()
42 else Hooks.continue ()
43
44 let hooks =
45 Hooks.empty
46 |> Hooks.add Hooks.Pre_tool_use [
47 Hooks.matcher ~pattern:"Bash" [block_rm_rf]
48 ]
49
50 let options = Claude.Options.create ~hooks:(Some hooks) () in
51 let client = Claude.Client.create ~options ~sw ~process_mgr () in
52 ]}
53*)
54
55(** The log source for hooks *)
56val src : Logs.Src.t
57
58(** {1 Hook Events} *)
59
60(** Hook event types *)
61type event =
62 | Pre_tool_use (** Fires before a tool is executed *)
63 | Post_tool_use (** Fires after a tool completes *)
64 | User_prompt_submit (** Fires when user submits a prompt *)
65 | Stop (** Fires when conversation stops *)
66 | Subagent_stop (** Fires when a subagent stops *)
67 | Pre_compact (** Fires before message compaction *)
68
69val event_to_string : event -> string
70val event_of_string : string -> event
71val event_jsont : event Jsont.t
72
73(** {1 Context} *)
74
75module Context : sig
76 type t = {
77 signal: unit option;
78 unknown : Unknown.t;
79 }
80
81 val create : ?signal:unit option -> ?unknown:Unknown.t -> unit -> t
82 val signal : t -> unit option
83 val unknown : t -> Unknown.t
84 val jsont : t Jsont.t
85end
86
87(** {1 Decisions} *)
88
89type decision =
90 | Continue (** Allow the action to proceed *)
91 | Block (** Block the action *)
92
93val decision_jsont : decision Jsont.t
94
95(** {1 Generic Hook Result} *)
96
97(** Generic result structure for hooks *)
98type result = {
99 decision: decision option;
100 system_message: string option;
101 hook_specific_output: Jsont.json option;
102 unknown: Unknown.t;
103}
104
105val result_jsont : result Jsont.t
106
107(** {1 Typed Hook Modules} *)
108
109(** PreToolUse hook - fires before tool execution *)
110module PreToolUse : sig
111 (** Typed input for PreToolUse hooks *)
112 type input = {
113 session_id: string;
114 transcript_path: string;
115 tool_name: string;
116 tool_input: Jsont.json;
117 unknown: Unknown.t;
118 }
119
120 type t = input
121
122 (** Parse hook input from JSON *)
123 val of_json : Jsont.json -> t
124
125 (** {2 Accessors} *)
126 val session_id : t -> string
127 val transcript_path : t -> string
128 val tool_name : t -> string
129 val tool_input : t -> Jsont.json
130 val unknown : t -> Unknown.t
131
132 val input_jsont : input Jsont.t
133
134 (** Permission decision for tool usage *)
135 type permission_decision = [ `Allow | `Deny | `Ask ]
136
137 val permission_decision_jsont : permission_decision Jsont.t
138
139 (** Typed output for PreToolUse hooks *)
140 type output = {
141 permission_decision: permission_decision option;
142 permission_decision_reason: string option;
143 updated_input: Jsont.json option;
144 unknown: Unknown.t;
145 }
146
147 val output_jsont : output Jsont.t
148
149 (** {2 Response Builders} *)
150 val allow : ?reason:string -> ?updated_input:Jsont.json -> ?unknown:Unknown.t -> unit -> output
151 val deny : ?reason:string -> ?unknown:Unknown.t -> unit -> output
152 val ask : ?reason:string -> ?unknown:Unknown.t -> unit -> output
153 val continue : ?unknown:Unknown.t -> unit -> output
154
155 (** Convert output to JSON for hook_specific_output *)
156 val output_to_json : output -> Jsont.json
157end
158
159(** PostToolUse hook - fires after tool execution *)
160module PostToolUse : sig
161 type input = {
162 session_id: string;
163 transcript_path: string;
164 tool_name: string;
165 tool_input: Jsont.json;
166 tool_response: Jsont.json;
167 unknown: Unknown.t;
168 }
169
170 type t = input
171
172 val of_json : Jsont.json -> t
173
174 val session_id : t -> string
175 val transcript_path : t -> string
176 val tool_name : t -> string
177 val tool_input : t -> Jsont.json
178 val tool_response : t -> Jsont.json
179 val unknown : t -> Unknown.t
180
181 val input_jsont : input Jsont.t
182
183 type output = {
184 decision: decision option;
185 reason: string option;
186 additional_context: string option;
187 unknown: Unknown.t;
188 }
189
190 val output_jsont : output Jsont.t
191
192 val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
193 val block : ?reason:string -> ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
194 val output_to_json : output -> Jsont.json
195end
196
197(** UserPromptSubmit hook - fires when user submits a prompt *)
198module UserPromptSubmit : sig
199 type input = {
200 session_id: string;
201 transcript_path: string;
202 prompt: string;
203 unknown: Unknown.t;
204 }
205
206 type t = input
207
208 val of_json : Jsont.json -> t
209
210 val session_id : t -> string
211 val transcript_path : t -> string
212 val prompt : t -> string
213 val unknown : t -> Unknown.t
214
215 val input_jsont : input Jsont.t
216
217 type output = {
218 decision: decision option;
219 reason: string option;
220 additional_context: string option;
221 unknown: Unknown.t;
222 }
223
224 val output_jsont : output Jsont.t
225
226 val continue : ?additional_context:string -> ?unknown:Unknown.t -> unit -> output
227 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
228 val output_to_json : output -> Jsont.json
229end
230
231(** Stop hook - fires when conversation stops *)
232module Stop : sig
233 type input = {
234 session_id: string;
235 transcript_path: string;
236 stop_hook_active: bool;
237 unknown: Unknown.t;
238 }
239
240 type t = input
241
242 val of_json : Jsont.json -> t
243
244 val session_id : t -> string
245 val transcript_path : t -> string
246 val stop_hook_active : t -> bool
247 val unknown : t -> Unknown.t
248
249 val input_jsont : input Jsont.t
250
251 type output = {
252 decision: decision option;
253 reason: string option;
254 unknown: Unknown.t;
255 }
256
257 val output_jsont : output Jsont.t
258
259 val continue : ?unknown:Unknown.t -> unit -> output
260 val block : ?reason:string -> ?unknown:Unknown.t -> unit -> output
261 val output_to_json : output -> Jsont.json
262end
263
264(** SubagentStop hook - fires when a subagent stops *)
265module SubagentStop : sig
266 include module type of Stop
267 val of_json : Jsont.json -> t
268end
269
270(** PreCompact hook - fires before message compaction *)
271module PreCompact : sig
272 type input = {
273 session_id: string;
274 transcript_path: string;
275 unknown: Unknown.t;
276 }
277
278 type t = input
279
280 type output = unit
281
282 val of_json : Jsont.json -> t
283
284 val session_id : t -> string
285 val transcript_path : t -> string
286 val unknown : t -> Unknown.t
287
288 val input_jsont : input Jsont.t
289
290 val continue : unit -> output
291 val output_to_json : output -> Jsont.json
292end
293
294(** {1 Callbacks} *)
295
296(** Generic callback function type.
297
298 Callbacks receive:
299 - [input]: Raw JSON input (parse with [PreToolUse.of_json], etc.)
300 - [tool_use_id]: Optional tool use ID
301 - [context]: Hook context
302
303 And return a generic [result] with optional hook-specific output.
304*)
305type callback =
306 input:Jsont.json ->
307 tool_use_id:string option ->
308 context:Context.t ->
309 result
310
311(** {1 Matchers} *)
312
313(** A matcher configuration *)
314type matcher = {
315 matcher: string option; (** Pattern to match (e.g., "Bash" or "Write|Edit") *)
316 callbacks: callback list; (** Callbacks to invoke on match *)
317}
318
319(** Hook configuration: map from events to matchers *)
320type config = (event * matcher list) list
321
322(** {1 Generic Result Builders} *)
323
324(** [continue ?system_message ?hook_specific_output ?unknown ()] creates a continue result *)
325val continue : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result
326
327(** [block ?system_message ?hook_specific_output ?unknown ()] creates a block result *)
328val block : ?system_message:string -> ?hook_specific_output:Jsont.json -> ?unknown:Unknown.t -> unit -> result
329
330(** {1 Configuration Builders} *)
331
332(** [matcher ?pattern callbacks] creates a matcher *)
333val matcher : ?pattern:string -> callback list -> matcher
334
335(** Empty hooks configuration *)
336val empty : config
337
338(** [add event matchers config] adds matchers for an event *)
339val add : event -> matcher list -> config -> config
340
341(** {1 JSON Serialization} *)
342
343val result_to_json : result -> Jsont.json
344val config_to_protocol_format : config -> Jsont.json