# EONYX Hooks: Automation Around Tool Calls


**Hooks** are shell commands that run **around a tool call**.
One before it, one after it.
They bolt on formatting, auditing and guardrails without touching the agent's code.

<!--more-->

## ⚙️ How hooks work

### Format

Hooks live in `eonyx.json`.
The key is the **tool name**.
The value is `before`, `after`, or both.

```json
"hooks": {
  "write": {
    "before": "echo \"writing: $EONYX_INPUT\" >> .eonyx/audit.log",
    "after":  "gofmt -l ."
  },
  "edit": {
    "after": "gofmt -l ."
  }
}
```

No events.
No matchers.
Everything hangs off the tool name: `write`, `edit`, `bash`, `git_commit`, MCP tools and the rest.

### The before and after phases

| Phase    | When                 | Effect                                                              |
| -------- | -------------------- | ------------------------------------------------------------------- |
| `before` | before the tool      | a non-zero exit code **blocks** the tool; the error goes to the model |
| `after`  | after a successful call | the output (stdout and stderr) is **appended** to the tool result (the model sees it) |

- An empty or missing hook does nothing.
- An error in an `after` hook does not break the result. It is kept with a `(hook after <tool> failed: …)` note.
- `after` does not run if the tool itself failed.
- Every hook runs in the shared shell session, with a **30 second** timeout.

{{< admonition type="tip" title="before can forbid things" >}}
A non-zero exit blocks the tool.
That makes `before` a guardrail.
Forbid `git_commit` when tests have not run.
Or forbid `bash` in a dangerous directory.
{{< /admonition >}}

### Context through variables

Data reaches the hook **through environment variables**.
Nothing is substituted into the command text.
So the input or the result cannot break your command, and injection is impossible.

| Variable        | What it holds                               |
| --------------- | ------------------------------------------- |
| `EONYX_TOOL`    | tool name                                   |
| `EONYX_PHASE`   | `before` or `after`                         |
| `EONYX_CWD`     | the shell's current working directory       |
| `EONYX_INPUT`   | the tool's raw JSON input                   |
| `EONYX_RESULT`  | the tool result (only in `after`)           |

## 🍳 Recipes

{{< tabs defaultTab="0" >}}
{{< tab title="Auto-format" markdown="true" >}}

Format the code after every edit:

```json
"hooks": {
  "write": { "after": "gofmt -w ." },
  "edit":  { "after": "gofmt -w ." },
  "multi_edit": { "after": "gofmt -w ." }
}
```

{{< /tab >}}
{{< tab title="Audit log" markdown="true" >}}

Log every shell command:

```json
"hooks": {
  "bash": { "before": "echo \"[$(date -Is)] $EONYX_INPUT\" >> .eonyx/bash-audit.log" }
}
```

{{< /tab >}}
{{< tab title="Guardrail" markdown="true" >}}

No commits until the tests are green:

```json
"hooks": {
  "git_commit": { "before": "go test ./... >/dev/null 2>&1 || { echo 'tests failing'; exit 1; }" }
}
```

The non-zero exit blocks the commit.
The message goes to the model.

{{< /tab >}}
{{< /tabs >}}

{{< admonition type="warning" title="Hooks are your code" >}}
Hooks run in the shared shell session, the same one the `bash` tool uses.
They run with your privileges.
Write them as carefully as any other script.
Keep them fast, under the 30s limit, and idempotent.
{{< /admonition >}}

## 📋 Summary

{{< admonition type="abstract" title="Cheat sheet" >}}
- **Format**
  - the key is the tool name, the value is `before`/`after`
  - no events and no matchers
- **Phases**
  - `before` blocks the tool with a non-zero exit code
  - `after` appends its output to the result
- **Context**
  - passed through `EONYX_*` variables
  - no substitution into the text — injection is impossible
- **Limits**
  - 30s timeout, shared shell session
  - `after` errors do not break the result
- **Recipes**
  - auto-format, audit log, blocking commits without tests
{{< /admonition >}}

