Chapter 7: jevhooks-events, the treaty
Two programs that talk over a socket have to agree on words. If the mod
sends "PreToolUse" and the daemon expects "pre_tool_use", nothing
crashes: the daemon just refuses the event, the mod reads the refusal as
"no opinion", and the plugin quietly stops doing anything. That is the
worst kind of bug, the kind that looks like everything is fine.
This crate is the treaty both sides sign. It is a handful of Rust types, and both the mod and the daemon are compiled from them: the mod into JavaScript (chapter 9), the daemon natively (chapter 11). Rename a variant here and both sides change in the same build. They cannot disagree, because there is only one of each word.
flowchart LR events["jevhooks-events<br/>HookEvent, Role, Verdict, Decision"] events -- "compiled to wasm, then JS" --> modjs["plugin/hooks/jevhooks.js"] events -- "compiled natively" --> daemon["plugin/bin/jevhooks"] modjs -- "JSON on a Unix socket" --> daemon
It depends on serde and nothing else: no network, no files, no clock. That
is not tidiness. The mod's copy has to run inside Claude Code as plain
JavaScript, where none of those exist.
Aside: serde. If you come from JavaScript,
serdeis the part of Rust thatJSON.stringifyandJSON.parseare built into. Putting#[derive(Serialize, Deserialize)]on a type writes both directions for you, and#[serde(rename_all = "lowercase")]decides thatVerdict::Asktravels as"ask".
Try it.
cargo test -p jevhooks-eventsruns its two tests: every event is listed once, and an event's JSON name is its hook name ("PreToolUse","Stop").
For the people who maintain it
The one place a word is not shared: the daemon's reply, a Decision, is
read on the TypeScript side through plugin/types/index.d.ts, which
mirrors it by hand (chapter 4).
In this folder
| Path | What |
|---|---|
| src/ | Chapter 8: the types. |
| Cargo.toml | The crate: serde, and serde_json for the tests. |
← Previous: Chapter 6, crates/ · Up: crates · Next: Chapter 8, jevhooks-events/src/ →