Hooks Reference
Hooks are shell scripts that fire during agent activity and write events to .moatlog/events-YYYY-MM-DD.jsonl. moatlog init wires them automatically — this page is for reference and customization.
How hooks are wired
Cursor
Config file: .cursor/hooks.json. Script location: .cursor/hooks/ (moatlog-event.sh, moatlog-distill.sh).
Cursor hook events that trigger moatlog and the moatlog action they produce:
| Cursor hook | Matcher | moatlog action |
|---|---|---|
beforeReadFile | — | read |
preToolUse | Read | read |
postToolUse | Read | read |
afterFileEdit | — | write |
beforeSubmitPrompt | — | prompt_start |
sessionStart | — | session_start |
sessionEnd | — | session_end |
afterShellExecution | — | shell |
stop | — | agent_stop |
stop | — | runs moatlog-distill.sh (moatlog distill) |
{
"version": 1,
"hooks": {
"beforeReadFile": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"preToolUse": [{ "command": ".cursor/hooks/moatlog-event.sh", "matcher": "Read" }],
"postToolUse": [{ "command": ".cursor/hooks/moatlog-event.sh", "matcher": "Read" }],
"afterFileEdit": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"beforeSubmitPrompt": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"sessionStart": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"sessionEnd": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"afterShellExecution": [{ "command": ".cursor/hooks/moatlog-event.sh" }],
"stop": [
{ "command": ".cursor/hooks/moatlog-event.sh" },
{ "command": ".cursor/hooks/moatlog-distill.sh" }
]
}
}Claude Code
Config file: .claude/settings.json (hooks key). Script location: .claude/hooks/ (moatlog-event-claude.sh, moatlog-distill-claude.sh). init adds .claude/hooks to .gitignore.
Claude Code hook events that trigger moatlog:
| Claude Code hook | Matcher | moatlog action |
|---|---|---|
PreToolUse | Read | read |
PreToolUse | Bash | shell |
PostToolUse | Write|Edit|MultiEdit | write |
UserPromptSubmit | — | prompt_start |
Stop | — | agent_stop |
Stop | — | runs moatlog-distill-claude.sh (agent_stop + moatlog distill) |
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/moatlog-event-claude.sh",
"timeout": 10
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/moatlog-event-claude.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/moatlog-event-claude.sh",
"timeout": 10
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/moatlog-event-claude.sh",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/moatlog-distill-claude.sh",
"timeout": 60
}
]
}
]
}
}Events JSONL schema
Each line in .moatlog/events-YYYY-MM-DD.jsonl is one JSON object appended by the hook scripts (EventLogger.write uses JSON.stringify with no file-level _version field). The contract is the AgentEvent interface in packages/core/src/types.ts, plus readSource on Cursor read events.
action (AgentAction)
read, write, create, delete, rename, prompt_start, agent_stop, session_end, event_log_boundary (synthetic — inserted between JSONL files during distill), shell. Hooks also emit session_start (Cursor sessionStart), which is not yet in the AgentAction union.
agent (AgentName)
cursor, claude-code, copilot. Hook scripts set cursor or claude-code.
Field reference
| Field | Type | Description | Example |
|---|---|---|---|
id | string | UUID per event | 9653487f-7976-4cd4-b0c7-c4b6c118e819 |
timestamp | string | ISO 8601 UTC | 2026-06-12T18:46:59.000Z |
sessionId | string | Agent session identifier | 7fed1384-1f3d-42cc-8714-94afe85f2dc2 |
agent | AgentName | Which agent produced the event | cursor |
action | AgentAction | Event kind (see action list above) | read |
projectName | string | Project directory name | moatlog |
path | string? | Absolute file path (read / write) | /…/docs/styles/tokens.css |
relativePath | string? | Project-relative path (read / write) | docs/styles/tokens.css |
extension | string? | File extension (read / write) | .css |
directory | string? | Parent directory relative to project root (read / write) | docs/styles |
generationId | string? | Cursor generation_id or Claude Code tool_use_id when available | 385340ec-a93b-4138-a88c-70e1b2523b73 |
task | string? | Raw prompt text (prompt_start only) | can we make this background… |
command | string? | Shell command text (shell only) | yarn test |
duration_ms | number? | Session duration (session_end only) | 0 |
reason | string? | Session end reason (session_end only) | user_close |
previousPath | string? | Prior path (rename only — not emitted by current hooks) | — |
readSource | string? | Which Cursor hook captured the read: beforeReadFile, preToolUse, or postToolUse | postToolUse |
Examples from event logs
Real lines from .moatlog/events-*.jsonl in the moatlog repo. create, delete, rename, and event_log_boundary do not appear in on-disk event logs — create/delete/rename are reserved in AgentAction; event_log_boundary is inserted in memory when distill reads multiple daily files.
read
{"id":"f6b1ff2a-3cb9-457f-b5b0-4664e354c634","timestamp":"2026-06-12T19:14:35.000Z","sessionId":"verify-read-session","agent":"cursor","action":"read","path":"/Users/bap5806/Documents/side-projects/moatlog/packages/cli/src/commands/status.ts","relativePath":"packages/cli/src/commands/status.ts","extension":".ts","directory":"packages/cli/src/commands","projectName":"moatlog","readSource":"postToolUse"}write
{"id":"cce63e77-f12c-4c38-9d61-50d574e06e6f","timestamp":"2026-06-11T05:26:41.000Z","sessionId":"8a88eb30-578a-4220-8789-0508c6d70653","agent":"cursor","action":"write","path":"/Users/bap5806/Documents/side-projects/moatlog/docs/lib/docs.ts","relativePath":"docs/lib/docs.ts","extension":".ts","directory":"docs/lib","projectName":"moatlog"}prompt_start
{"id":"10c92fde-ebf5-4733-81da-fdf59c1d908f","timestamp":"2026-06-14T00:15:05.000Z","sessionId":"7fed1384-1f3d-42cc-8714-94afe85f2dc2","generationId":"385340ec-a93b-4138-a88c-70e1b2523b73","agent":"cursor","action":"prompt_start","task":"can we make this background for the light modeF1F1ED","projectName":"moatlog"}agent_stop
{"id":"6b76f289-0501-4273-bcc3-1f7824f26ba2","timestamp":"2026-06-14T04:32:31.000Z","sessionId":"7fed1384-1f3d-42cc-8714-94afe85f2dc2","generationId":"7f0fc178-492a-45d1-89e0-7692e8ad0f6e","agent":"cursor","action":"agent_stop","projectName":"moatlog"}shell
{"id":"870dfb1f-b7ba-4585-80f4-b461a84440f2","timestamp":"2026-06-11T22:15:10.000Z","sessionId":"8a88eb30-578a-4220-8789-0508c6d70653","agent":"cursor","action":"shell","command":"TMP=$(mktemp -d) && cd \"$TMP\" && node /Users/bap5806/Documents/side-projects/moatlog/packages/cli/dist/bin.js init && echo \"---\" && ls -la .cursor/hooks/ .cursor/rules/ && cat .cursor/mcp.json && grep \"PROJECT_NAME=\" .cursor/hooks/moatlog-event.sh | head -1 && test -x .cursor/hooks/moatlog-event.sh && echo \"executable ok\" && rm -rf \"$TMP\"","projectName":"moatlog"}session_start
{"id":"901e1713-c464-4beb-bd5b-de1adc48653f","timestamp":"2026-06-12T18:54:31.000Z","sessionId":"7fed1384-1f3d-42cc-8714-94afe85f2dc2","agent":"cursor","action":"session_start","projectName":"moatlog"}session_end
{"id":"de1f6026-efd1-47fa-b117-31c928b8b3ea","timestamp":"2026-06-11T22:22:44.000Z","sessionId":"8a88eb30-578a-4220-8789-0508c6d70653","agent":"cursor","action":"session_end","duration_ms":0,"reason":"user_close","projectName":"moatlog"}.moatlogignore
.moatlogignore uses .gitignore syntax. Files matching patterns are excluded at the hook layer (never written to JSONL) and at the distill layer. moatlog init writes .moatlogignore automatically.
Default patterns (from DEFAULT_MOATLOGIGNORE_PATTERNS in packages/core/src/moatlogignore.ts, plus .moatlog/ added by init):
.env*
*.pem
*.key
id_rsa*
*credentials*
.npmrc
.moatlog/Verifying hooks are active
- Run
moatlog status— should show ● hooks active (.cursor/hooks.json) - Run
moatlog doctorfor a full health check (hooks, read capture, MCP, moat.json, permissions) - Check
.moatlog/events-YYYY-MM-DD.jsonldirectly — events should appear within seconds of agent activity - If hooks aren't firing, see MCP Setup → Troubleshooting
Customizing hooks
Advanced users can edit the hook scripts in .cursor/hooks/ or .claude/hooks/ directly. moatlog init --force will overwrite customizations to those scaffolded files. If you need custom logic, keep it in a separate script and call it from the moatlog hook script.