> Section: [4. Configuration](https://jaque.sh/docs/config/cue-in-practice.md)
> Next: config/hot-reload
> Index: https://jaque.sh/llms.txt


jaque runs a command when an object changes state, so an operator can
react automatically: restart a daemon on the first soft failure, tell an
orchestrator once a host goes hard down. Handlers run regardless of
acknowledgements, downtimes and flapping, and they are told the object's
state, state type and attempt number as macros. This is the same
mechanism Nagios calls event handlers, and jaque expresses it as config
objects and a role of its own that runs them.

## 1. What fires

A handler fires on a `StateChanged` fact, never on every check result,
following this table:

| From | To | Trigger |
|---|---|---|
| anything | soft problem | `SOFT`, on every attempt |
| soft, or a different status | hard problem | `HARD` |
| problem, soft or hard | `OK` | `RECOVERY` |
| same status and type (flapping or attempt-only change) | | none |

This matches Nagios's own rule for when `event_handler` runs, with one
addition: jaque also fires on a hard-to-hard status change. Handlers run
in downtime, the way Nagios runs them, because a downtime silences
notifications and leaves the state changes handlers react to untouched.

## 2. The `handlers` and `event_handlers` blocks

```cue
handlers: {
	"restart-httpd": {
		type:    "command"
		command: "restart-service"     // key into commands
		args:    ["httpd"]
		timeout: "30s"                 // Nagios event_handler_timeout
	}
	"page-orchestrator": {
		type:   "wasm"
		module: "/opt/jaque/handlers/page.wasm"
		args:   ["--cluster", "eu1"]
	}
	"notify-ops": {
		type: "legacy"
		path: "/usr/local/bin/notify-ops"
		args: ["$HOSTNAME$", "$SERVICESTATE$"]
	}
}

event_handlers: {
	enabled: true                       // enable_event_handlers
	host:    "page-orchestrator"        // global_host_event_handler
	service: ""                         // global_service_event_handler
}

hosts: {
	web01: {
		handler:         "restart-httpd"   // event_handler
		handler_enabled: true              // event_handler_enabled
		services: {
			http: {handler: "restart-httpd"}
		}
	}
}
```

A handler is one of the three command shapes a check already has,
`legacy`, `command` or `wasm`, plus `timeout` (Nagios's
`event_handler_timeout`, `30s` by default). Unlike a `command` check,
whose macros are all known when config loads, a handler's line or
arguments may name `$HOSTSTATE$` and friends: those are dispatch-time
macros, so config keeps the template unexpanded and validates only that
the handler name and, for a `command` handler, the command name resolve.
A host or service names its own handler with `handler`; `event_handlers`
names the globals, Nagios's `global_host_event_handler` and
`global_service_event_handler`.

## 3. Order: global first, own second

When an object has both a global handler for its kind and its own
`handler`, both run on the same trigger, the global one first and the
object's own second: Nagios's order. `event_handlers.enabled: false`
switches handlers off everywhere; `handler_enabled: false` on a host or
service switches them off for that object alone, overriding whichever
handlers would otherwise apply to it.

## 4. Macros available at fire time

A handler template sees the same dispatch-time macros a notification
command does: `$HOSTSTATE$`/`$SERVICESTATE$` (`UP`/`DOWN` for a host,
the status name for a service), `$HOSTSTATETYPE$`/`$SERVICESTATETYPE$`
(`SOFT`/`HARD`), `$HOSTATTEMPT$`/`$SERVICEATTEMPT$`,
`$HOSTOUTPUT$`/`$SERVICEOUTPUT$` and
`$HOSTPERFDATA$`/`$SERVICEPERFDATA$` (the last completed check's
result), `$LASTHOSTSTATECHANGE$`/`$LASTSERVICESTATECHANGE$`, and the
five date macros (`$TIMET$`, `$DATE$`, `$TIME$`, `$SHORTDATETIME$`,
`$LONGDATETIME$`). A template whose expansion fails, naming a macro the
object cannot provide, runs nothing: the failure is recorded as a run
with status `UNKNOWN` and the error as the output, so the audit shows
the handler was due and why it did not run.

## 5. What is recorded

Every run, executed or failed to expand, is a `handler_executed` event
on the log carrying the handler name, the trigger and the result
(status, output, latency); see [Event log
schema](../reference/event-log-schema.md). It is never folded into
projected state: replay and every projection ignore it, and it exists
for the audit trail. The same run is logged as one line with the
object, handler, trigger, status and latency, and counted by
`jaque_eventhandler_runs_total{outcome}`; a shard left blocked or an
event fenced by a stale membership epoch is counted by
`jaque_eventhandler_blocked_total` and `jaque_eventhandler_fenced_total`.
See [Metrics](https://jaque.sh/docs/observability/metrics.md).

## 6. Where it runs

Handlers run under their own role, `-target eventhandler`, embedded in
`-target all` and `-target engine` by default and moved out with
`-eventhandler-execution external`. See [Topologies](https://jaque.sh/docs/deployment/topologies.md)
section 1.7 for its flags and how it composes with the other roles, and
[Security](https://jaque.sh/docs/deployment/security.md) for the trust a handler command
runs with.

## 7. Importing from Nagios

`event_handler` on a host or service becomes a `handlers` entry keyed by
the directive's value verbatim, rendered as a `command` handler with the
`!`-split arguments, the same split `check_command` gets, and
`handler: "<value>"` on the object; two objects naming the same value
share the entry. `event_handler_enabled 0` becomes `handler_enabled:
false`. From the main config, `enable_event_handlers`,
`global_host_event_handler` and `global_service_event_handler` fill the
`event_handlers` block, and `event_handler_timeout` fills each rendered
handler's `timeout`. A handler naming a command the tree does not define
is reported unsupported, the way an undefined check command is.
`event_handler_period` stays unsupported: it is Icinga 1's, and Nagios
has no such directive. See [Importer](https://jaque.sh/docs/nagios-compat/importer.md)
section 4.
