Twenty years of Nagios plugins exist: check_mk agents, vendor scripts, the Perl thing a former colleague wrote in 2009. A legacy check runs any of them unmodified, because the protocol they speak (exit code, stdout, perfdata after a |) is frozen in jaque and supported forever. The cost is one process per execution and code running with the engine's own permissions; the benefit is the sector's largest check ecosystem, working on day one, with nothing rewritten.

1. The protocol, unchanged

A legacy check executes a binary and reads its result the way Nagios always has. Exit code 0, 1, 2 or 3 maps to OK, WARNING, CRITICAL or UNKNOWN. Stdout is the check output; the first | splits it into text and perfdata, the same convention every other check type in jaque follows.

services: disk: check: {
	type: "legacy"
	path: "/usr/lib/nagios/plugins/check_disk"
	args: ["-w", "20%", "-c", "10%", "-p", "/"]
}

path is executed directly with args as its argument vector. There is no shell in between; a plugin invocation that needs a pipe or a redirect is a command check with shell: true.

2. Perfdata grammar

The text after | is parsed as zero or more whitespace-separated metrics, each of the form:

label=value[UOM];warn;crit;min;max

2.1 Label

label is everything before the first =. A label that starts with a single quote (') runs to the next unescaped ' and may contain spaces; a doubled '' inside a quoted label is a literal quote, not a close. Space inside an unquoted label ends the token early and is not supported.

2.2 Value and unit

value is an int or float, optionally signed, optionally with an exponent (1, -0.5, 1e3), immediately followed by an optional UOM made only of letters, % and /. A token whose unit contains anything else fails to parse.

2.3 Thresholds and bounds

;warn;crit;min;max are semicolon-separated and optional from the right: label=value alone is valid, as are label=value;warn, label=value;warn;crit and so on up to all four. warn and crit are Nagios threshold range specs; min and max are plain floats. A token with more than five ;-separated fields fails to parse.

2.4 Error handling

Metrics are separated by whitespace outside quoted labels. A malformed token is dropped without failing the metrics around it, so one bad field from a plugin does not cost the sink the rest of the line.

time=0.125s;1.0;2.0;0;5 'packet loss'=0%;20;40

3. Process isolation

Each execution runs in its own process group (Setpgid). On timeout jaque kills the whole group, not the immediate child, so a plugin that forks a helper and then hangs leaves no orphan behind. timeout is per check and defaults to 10s from #Schedule. A plugin that times out, cannot be found, is not executable, or dies on a signal is reported as UNKNOWN with the reason in its output; it is never retried into a false OK.

4. Permanent, not a bridge

Legacy exec support is not a migration aid to be outgrown. Anything that speaks the exit-code protocol keeps working indefinitely; this is the same commitment the landing page makes and the status page tracks. WASM checks exist beside it for code written new, that should be one file and sandboxed by construction; they do not replace it.

5. Security considerations

A legacy plugin is arbitrary code with the engine process's user, ambient environment and network position. jaque constrains its lifetime (the process group and timeout) and nothing else; what it may read or reach is the operator's job, through the user jaque runs as. args are passed as an argument vector, never through a shell, so a value in config cannot inject a second command.