# Hook events

> How each agent's native hook events map to Comity events, what is sent, timeouts, and how answers are rendered.

Every agent integration runs `comity hook --tool <tool> --event <Event>` with the host's hook JSON on stdin (the omp extension calls the same code in-process). The CLI maps the native event to a Comity event, sends it to `POST /v1/hooks/evaluate`, and prints the host's native hook output.

## Event mapping

| Comity event | Claude Code / Copilot CLI | Codex | Cursor (via Claude hooks) | omp |
|---|---|---|---|---|
| `session_start` | `SessionStart` | `SessionStart` | `sessionStart` | `session_start` |
| `prompt` | `UserPromptSubmit` | `UserPromptSubmit` | `beforeSubmitPrompt` | `before_agent_start` |
| `plan` | `PreToolUse` on `ExitPlanMode` | – | – | – |
| `pre_edit` | `PreToolUse` on an edit tool | `PreToolUse` on `apply_patch` / edit | `preToolUse` on an edit tool | `tool_call` on `edit`, `apply_patch`, `write` |
| `pre_shell` | `PreToolUse` on `Bash` | `PreToolUse` on a shell command | `preToolUse` on `Shell` | `tool_call` on `bash`, `eval`, `ast_edit` |
| `post_edit` | `PostToolUse` on an edit tool or `Bash` | `PostToolUse` | `postToolUse` | `tool_result` on the same tools |
| `stop` | `Stop` | `Stop` | `stop` | `session_stop` |
| `session_end` | `SessionEnd` | `SessionEnd` | `sessionEnd` | `session_shutdown` |

- **Edit tools**: `Edit`, `Write`, `MultiEdit`, `NotebookEdit` (and `Delete` where a host has it). Host-specific names are normalized: `create`/`write` → `Write`, `edit`/`str_replace_editor` → `Edit`, `Shell`/`bash`/`powershell`/`shell`/`exec_command` → `Bash`.
- **Codex `apply_patch`**: paths come from the patch envelope (`*** Update File: …`). A shell command containing `*** Begin Patch` is treated as an edit of those files.
- **Other tools** (reads, searches, web) are ignored and never leave the machine.
- The matchers `comity init` writes are `Edit|Write|MultiEdit|NotebookEdit|Bash|ExitPlanMode` on `PreToolUse` and `Edit|Write|MultiEdit|NotebookEdit|Bash` on `PostToolUse` (Claude Code), and `apply_patch|Edit|Write|Bash` (Codex). Hooks written by CLI 0.4.0 or older lack `ExitPlanMode`, so they never send `plan`; they keep working otherwise, and `comity doctor` shows "update available" until you rerun `comity init`. Codex has no plan event.
- `pre_push` exists in the protocol but no integration sends it and the server doesn't act on it.

## What each event sends

| Event | Extra fields |
|---|---|
| `session_start` | Workspace packages (`repo.packages`, up to 200) |
| `prompt` | `prompt_excerpt`: first 2,000 characters of the prompt |
| `plan` | `plan_text` (up to 32,768 characters) |
| `pre_edit` | `paths`: repo-relative paths about to change (up to 500) |
| `pre_shell` | `shell.command` (up to 4,000 characters); `paths` the command visibly writes (`>`, `>>`, `tee`) |
| `post_edit` | `paths` changed (for shell commands: the working-tree diff before/after); `analysis`: per-file symbols, signature hashes, compatibility, new references |
| `stop` | `status`: `{excerpt (≤500 chars of the last assistant message), files (touched this turn)}`, unless `COMITY_SHARE_STATUS=0` |

Every request also carries `session` (`external_id`, `tool`, `model`, `surface`, `parent_external_id` for subagents), `client` (`cli_version`, `os`, `tool_version`, `machine_id_hash`), `repo` (`remote_fingerprints`, `branch`, `head_sha`, `base_sha`, `worktree_hash`, `cwd_path`), and a `dedupe_key` so a retried event is evaluated once.

Hooks only run in git repositories with at least one remote. In a folder that contains several repositories (not itself a repo), the CLI sends each file's event to the repo it belongs to, and merges the briefings for up to 8 recently active repos at session start.

## Response

```json
{
  "decision": "warn",
  "reason": "1 coordination warning(s): …",
  "inject_context": "## Comity update — system-generated for `acme/api`\n…",
  "conflicts": [{ "number": 9, "ref": "CF-9", "kind": "symbol_overlap", "severity": "warn" }],
  "intent": { "number": 14, "ref": "INT-14", "status": "active" },
  "session_id": "0192f0c4-7d1e-7a8b-9c2d-3e4f5a6b7c8d",
  "index_version": 412
}
```

How the CLI renders it for the host:

| Result | Before a tool call (`PreToolUse`) | Other events |
|---|---|---|
| `block` | `{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "<reason>"}}` | Never happens |
| `warn` / context | Codex, omp: `additionalContext`. Claude Code, Cursor, Copilot CLI: queued and added to the next `PostToolUse` or prompt | `{"hookSpecificOutput": {"hookEventName": "<Event>", "additionalContext": "<text>"}}` on `SessionStart`, `UserPromptSubmit`, `PostToolUse` |
| `allow`, nothing to say | No output | No output |

Comity never outputs a native "allow" decision, so the host's own permission prompts still apply. Added context is capped at 6,000 characters per event. The hook always exits `0`.

## Timeouts

| | CLI waits for the API | Host hook timeout in `init` config |
|---|---|---|
| `pre_edit`, `pre_shell` | 400 ms | 5 s |
| Other events | 1.5 s | 5 s (`SessionStart`, `UserPromptSubmit`), 10 s (`PostToolUse`, `Stop`, `SessionEnd`; Codex `SessionEnd` 3 s) |
| omp extension | 600 ms per event, then allows | – |

If the API doesn't answer in time, errors, or returns an unexpected shape, the hook prints nothing (the agent proceeds) and appends a `fail-open` line to `hook.log` in the CLI config folder.

## Host detection

Cursor and Copilot CLI run the Claude Code hooks, so `--tool claude_code` is only a hint. The CLI decides from the payload and environment:

| Host | Recognized by |
|---|---|
| Cursor | `cursor_version` or `conversation_id` in the payload, or `CURSOR_VERSION` set |
| Copilot CLI | `sessionId` / `toolName` / `toolArgs` in the payload, or the VS Code–compatible payload (ISO `timestamp`, no `transcript_path`) |
| Codex | `--tool codex` or `turn_id` in the payload |
| omp | `--tool omp` (only the omp extension sets it) |
| Claude Code | otherwise |

If the detected host has its own Comity hook config (for example a `.cursor/hooks.json` or `.github/hooks/*.json` that calls `comity hook`), the invocation from the Claude Code config steps aside so each event is evaluated once.
