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 hookMatchermoatlog action
beforeReadFileread
preToolUseReadread
postToolUseReadread
afterFileEditwrite
beforeSubmitPromptprompt_start
sessionStartsession_start
sessionEndsession_end
afterShellExecutionshell
stopagent_stop
stopruns moatlog-distill.sh (moatlog distill)
json
{
  "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 hookMatchermoatlog action
PreToolUseReadread
PreToolUseBashshell
PostToolUseWrite|Edit|MultiEditwrite
UserPromptSubmitprompt_start
Stopagent_stop
Stopruns moatlog-distill-claude.sh (agent_stop + moatlog distill)
json
{
  "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

FieldTypeDescriptionExample
idstringUUID per event9653487f-7976-4cd4-b0c7-c4b6c118e819
timestampstringISO 8601 UTC2026-06-12T18:46:59.000Z
sessionIdstringAgent session identifier7fed1384-1f3d-42cc-8714-94afe85f2dc2
agentAgentNameWhich agent produced the eventcursor
actionAgentActionEvent kind (see action list above)read
projectNamestringProject directory namemoatlog
pathstring?Absolute file path (read / write)/…/docs/styles/tokens.css
relativePathstring?Project-relative path (read / write)docs/styles/tokens.css
extensionstring?File extension (read / write).css
directorystring?Parent directory relative to project root (read / write)docs/styles
generationIdstring?Cursor generation_id or Claude Code tool_use_id when available385340ec-a93b-4138-a88c-70e1b2523b73
taskstring?Raw prompt text (prompt_start only)can we make this background…
commandstring?Shell command text (shell only)yarn test
duration_msnumber?Session duration (session_end only)0
reasonstring?Session end reason (session_end only)user_close
previousPathstring?Prior path (rename only — not emitted by current hooks)
readSourcestring?Which Cursor hook captured the read: beforeReadFile, preToolUse, or postToolUsepostToolUse

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

json
{"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

json
{"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

json
{"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

json
{"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

json
{"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

json
{"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

json
{"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):

text
.env*
*.pem
*.key
id_rsa*
*credentials*
.npmrc
.moatlog/

Verifying hooks are active

  • Run moatlog status — should show ● hooks active (.cursor/hooks.json)
  • Run moatlog doctor for a full health check (hooks, read capture, MCP, moat.json, permissions)
  • Check .moatlog/events-YYYY-MM-DD.jsonl directly — 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.