> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enconvo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Run commands or webhooks automatically when EnConvo events happen

## Overview

Hooks let you run a shell command or send an HTTP request when something happens
inside EnConvo, such as an agent starting, completing, failing, or using a tool.
They are configured with a JSON file, so you can add notifications, logs, local
automation, or integrations without building an extension.

<Tip>
  Hooks are fire-and-forget. They run in the background and do not block EnConvo.
</Tip>

## Config File

Create your hooks config at:

```text theme={null}
~/.enconvo/hooks.json
```

EnConvo also reads the legacy file at `~/.config/enconvo/hooks.json` when the
new file does not exist. If both files exist, `~/.enconvo/hooks.json` wins.

The file is checked again whenever an event fires, so you can edit hooks while
EnConvo is running. No restart is required.

## Manage Hooks in Settings

Open **Settings → Hooks** to inspect hooks from `~/.enconvo/hooks.json`.
The settings panel groups entries by event, lets you toggle an entry on or off,
and can open the JSON file for editing. Toggling writes `enabled: false` or
`enabled: true` on that hook entry without changing the command or webhook
definition.

## Quick Start

<Steps>
  <Step title="Create the hooks folder">
    ```bash theme={null}
    mkdir -p ~/.enconvo
    ```
  </Step>

  <Step title="Create ~/.enconvo/hooks.json">
    ```json theme={null}
    {
      "hooks": {
        "UserPromptSubmit": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "echo 'Agent status changed' >> /tmp/enconvo-hooks.log"
              }
            ]
          }
        ]
      }
    }
    ```
  </Step>

  <Step title="Trigger an agent">
    Start any agent or chat command. When its status changes, EnConvo runs the
    configured command and appends a line to `/tmp/enconvo-hooks.log`.
  </Step>
</Steps>

## How Hooks Work

When an event fires, EnConvo:

1. Loads `~/.enconvo/hooks.json`.
2. Falls back to `~/.config/enconvo/hooks.json` only if the new file is absent.
3. Finds entries inside the top-level `hooks` object whose key matches the event name.
4. Applies the optional `matcher` regex.
5. Skips entries where `enabled` is `false`.
6. Runs every configured hook concurrently.

Hook event names follow the Codex lifecycle set: `SessionStart`,
`UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, and
`Stop`. The old EnConvo event names and old flat JSON shape are still readable
for existing installs, but new writes use the wrapped Codex-style format.

## Configuration Reference

```json theme={null}
{
  "hooks": {
    "event_name": [
      {
        "matcher": "optional_regex_filter",
        "enabled": true,
        "hooks": [
          { "type": "command", "command": "..." },
          { "type": "http", "url": "..." }
        ]
      }
    ]
  }
}
```

| Field        | Type    | Required | Description                                                                                                                     |
| ------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `hooks`      | object  | Yes      | Event map keyed by event name.                                                                                                  |
| `event_name` | string  | Yes      | Event to listen for. Use this as a key inside `hooks`.                                                                          |
| `matcher`    | string  | No       | Regex filter. For tool events, it matches `toolName`, `toolTitle`, or `path`; for other events, it matches the hook event name. |
| `enabled`    | boolean | No       | Set to `false` to keep the entry in config but skip execution. Defaults to `true`.                                              |
| `hooks`      | array   | Yes      | Command and HTTP hooks to execute.                                                                                              |

### Command Hook

A command hook runs a shell command. EnConvo writes the event payload to the
command's stdin as JSON.

```json theme={null}
{
  "type": "command",
  "command": "bash ~/.enconvo/hooks/on-status-change.sh",
  "timeout": 30000
}
```

| Field     | Type        | Default | Description               |
| --------- | ----------- | ------- | ------------------------- |
| `type`    | `"command"` | -       | Required hook type.       |
| `command` | string      | -       | Shell command to execute. |
| `timeout` | number      | `30000` | Timeout in milliseconds.  |

### HTTP Hook

An HTTP hook sends the event payload as the JSON request body.

```json theme={null}
{
  "type": "http",
  "url": "https://example.com/webhook",
  "method": "POST",
  "headers": {
    "Authorization": "Bearer your-token"
  },
  "timeout": 30000
}
```

| Field     | Type     | Default  | Description                 |
| --------- | -------- | -------- | --------------------------- |
| `type`    | `"http"` | -        | Required hook type.         |
| `url`     | string   | -        | HTTP endpoint URL.          |
| `method`  | string   | `"POST"` | HTTP method.                |
| `headers` | object   | `{}`     | Additional request headers. |
| `timeout` | number   | `30000`  | Timeout in milliseconds.    |

## Event Payload

Command hooks receive this JSON on stdin. HTTP hooks receive the same JSON as
the request body.

```json theme={null}
{
  "event": "UserPromptSubmit",
  "fullEvent": "event-UserPromptSubmit",
  "sourceEvent": "run_status_changed",
  "sourceFullEvent": "event-run_status_changed",
  "params": {
    "commandKey": "agent|main",
    "runStatus": "running"
  },
  "timestamp": 1742313600000
}
```

| Field             | Description                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `event`           | Short event name.                                                                                       |
| `fullEvent`       | Internal event name with the `event-` prefix.                                                           |
| `sourceEvent`     | Original EnConvo event that produced this hook event, when the event is mapped for Codex compatibility. |
| `sourceFullEvent` | Original internal event name with the `event-` prefix.                                                  |
| `params`          | Event-specific payload.                                                                                 |
| `timestamp`       | Unix timestamp in milliseconds.                                                                         |

## Available Events

| Event               | Params                                                                  | When                                                  |
| ------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
| `SessionStart`      | `{ command, commandKey }`                                               | A new chat session is created.                        |
| `UserPromptSubmit`  | `{ commandKey, runStatus, status?, sessionId? }`                        | An agent, chat, or bot run starts from user input.    |
| `PreToolUse`        | `{ toolCallId, toolName, toolTitle?, status, sessionId?, commandKey? }` | A tool call starts.                                   |
| `PermissionRequest` | `{ toolCallId, toolName, toolTitle?, status, sessionId?, commandKey? }` | A tool call reaches a permission or denied state.     |
| `PostToolUse`       | `{ toolCallId, toolName, toolTitle?, status, sessionId?, commandKey? }` | A tool call succeeds, fails, completes, or is denied. |
| `Stop`              | `{ commandKey, runStatus, status?, sessionId? }`                        | An agent, chat, or bot run reaches a terminal status. |

<Note>
  Legacy EnConvo names such as `run_status_changed` and
  `agent_tool_step_changed` still work, so existing hook files do not need to be
  migrated immediately.
</Note>

## Recipes

### macOS Notification on Agent Failure

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "STATUS=$(cat | jq -r '.params.runStatus // .params.status') && [ \"$STATUS\" = \"failed\" ] && osascript -e 'display notification \"An agent failed\" with title \"EnConvo\"'"
          }
        ]
      }
    ]
  }
}
```

### Log Agent Status Changes

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{time: (.timestamp / 1000 | strftime(\"%Y-%m-%d %H:%M:%S\")), agent: .params.commandKey, status: (.params.runStatus // .params.status)}' >> ~/.enconvo/agent-status.log"
          }
        ]
      }
    ]
  }
}
```

### Log Agent Tool Calls

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.params | \"\\(.status) \\(.toolName) \\(.toolCallId)\"' >> ~/.enconvo/agent-tools.log"
          }
        ]
      }
    ]
  }
}
```

### Send to Slack

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
          }
        ]
      }
    ]
  }
}
```

### Custom Script

Create a script that reads the event payload from stdin:

```bash theme={null}
#!/bin/bash
# ~/.enconvo/hooks/on-status-change.sh
INPUT=$(cat)
STATUS=$(echo "$INPUT" | jq -r '.params.runStatus // .params.status')
AGENT=$(echo "$INPUT" | jq -r '.params.commandKey')

echo "[$(date)] $AGENT -> $STATUS" >> /tmp/enconvo-hooks.log

if [ "$STATUS" = "failed" ]; then
  osascript -e "display notification \"$AGENT failed\" with title \"EnConvo\""
fi
```

Reference it from `~/.enconvo/hooks.json`:

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.enconvo/hooks/on-status-change.sh"
          }
        ]
      }
    ]
  }
}
```

Make the script executable:

```bash theme={null}
chmod +x ~/.enconvo/hooks/on-status-change.sh
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="My hook did not run">
    Confirm that `~/.enconvo/hooks.json` is valid JSON, the top-level key matches
    the event name, and the command works when run manually. If you still have
    `~/.config/enconvo/hooks.json`, remember that it is ignored when the new file
    exists.
  </Accordion>

  <Accordion title="How do I test a script manually?">
    Pipe a sample payload into the script:

    ```bash theme={null}
    echo '{"event":"Stop","params":{"commandKey":"agent|main","runStatus":"failed"},"timestamp":1742313600000}' | bash ~/.enconvo/hooks/on-status-change.sh
    ```
  </Accordion>

  <Accordion title="Do hooks block EnConvo?">
    No. Hooks are fire-and-forget. If a hook fails or times out, EnConvo logs the
    failure and continues processing the original event.
  </Accordion>

  <Accordion title="Should commands use absolute paths?">
    Prefer absolute paths for scripts and binaries. Shell `~` expansion works
    when the command is executed through a shell, but absolute paths are easier
    to debug.
  </Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="Agents" icon="robot" href="/ai/agents">
    Learn about EnConvo agents and custom bots
  </Card>

  <Card title="Extension API Reference" icon="code" href="/extensions/extension-api-reference">
    Build extensions that emit or listen to events
  </Card>
</CardGroup>
