Custom Script Check Reference

Field-level reference for the Custom Script check — wrapper contract, helpers, input/output schema, status codes, and execution limits.

Written By Erdinc Akay

Last updated 25 days ago

octoja supports two distinct ways to ship a script as a check, and they are not the same plugin under the hood. This reference partitions them clearly so you can configure the right one without surprises.

  • Built-in Custom Script check — a stock check plugin you assign to a device through a configuration package. You paste a single PowerShell or bash script into the check definition; the agent runs it as-is and converts the exit code into a status.
  • User-defined custom checks — checks you author yourself on the Custom Checks page of the dashboard. Each script is wrapped at distribution time by octoja so it can declare typed inputs, emit typed outputs, and use the bundled SNMP worker.

The two paths share neither the status-mapping mechanism nor the output contract. Read the section that matches the check you are configuring.

Built-in Custom Script check

Plugin identifier octo/custom-script. The agent runs the script in-process via powershell.exe on Windows or /bin/bash -s on Linux and macOS. No wrapper, no helpers, no sentinel markers — the check works entirely off the script's exit code and a small stdout/stderr capture.

Status codes (exit-code mapping)

The exit code drives the result. Each Custom Script check carries a SuccessExitCode, a WarningExitCodes list, and a CriticalExitCodes list:

FieldDefaultMeaning
SuccessExitCode0Status Success.
WarningExitCodes["1"]Status Warning.
CriticalExitCodes["2"]Status Critical.
Any other exit codeStatus Critical (unknown exit codes fall through to Critical, not Failure).

You can override any list per check — for example, accept both 0 and 3 as Success when wrapping a legacy tool. The critical list is evaluated before the warning list, so an exit code that appears in both maps to Critical. There is no script-side helper for this plugin; the result is always whatever the process returns.

What you get back

The check ships these fields to octoja on every run:

FieldContents
ExitCodeThe integer exit code; -1 if the script timed out or the process crashed.
StdoutFirst 2 KB of stdout. Longer output is truncated and suffixed with ... (truncated).
StderrFirst line of stderr, up to 500 characters. Truncated suffix as above.
ExecutionTimeMsWall-clock runtime in milliseconds.

Execution limits

LimitValueSource
Script size64 K characters (65,536) — longer scripts are rejected with status Warning before executionFixed limit
TimeoutPer-check via TimeoutSeconds; default 30 s, clamped to the range 5–300 sPer-check setting
Stdout captured2 KB (the rest is dropped, not Failure)Fixed limit
Stderr capturedFirst line, max 500 charactersFixed limit

What breaks the check

PatternResult
Empty script bodyFailure before execution
Script longer than 64 K charactersWarning before execution (the agent will not run it)
Runtime exceeds the configured TimeoutSecondsFailure; the agent kills the process and reports ExitCode = -1
Process launch crashes (interpreter missing, permission denied, etc.)Failure

User-defined custom checks

You author these checks on the dashboard's Custom Checks page — the editor covers scripts per platform, typed inputs, typed outputs, and the execution interval. (Community check repositories, added by name and URL, deliver ready-made checks from an external source; they are not how you author your own.) Each check is wrapped by octoja before it reaches the agent. The wrapper exposes typed inputs to your script, gives you helpers for emitting typed outputs, and delimits the result payload with sentinel markers so anything else you print to stdout cannot corrupt it.

FieldValue
Default interval5 minutes (configurable per check, 5–1440 minutes)
Maximum runtime5 minutes per execution
Platform supportWindows (PowerShell), Linux (bash), macOS (bash + Python 3)
Runtime IDswin-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64
Linux dependenciesjq — a hard dependency of every Linux custom check: the wrapper's output footer serializes the result payload with jq, and get_input / the SNMP helpers use it too
macOS dependenciespython3 (pre-installed on modern macOS)

If you provide only a Linux script, the agent runs it on macOS too via osx-x64 / osx-arm64 fallback.

The wrapper contract

The wrapper does three things around your script: it decodes the configured inputs and exposes them ($checkInput / INPUT_JSON), pre-creates the output container with checkResult = 0 ($output / _OCTO_OUTPUT), and after your script runs it serializes the output to base64 and prints it between the result markers. Your job is the middle: read inputs, do work, set output fields and the status. The agent extracts only the bytes between <<<OCTO_RESULT_BEGIN>>> and <<<OCTO_RESULT_END>>>, so Write-Output, Write-Host, echo, or ConvertTo-Json from your script do not break the wrapper.

Status — the checkResult sentinel

Wrapped checks do not use exit-code mapping. Status is the checkResult field on the serialized output payload, which the agent decodes into a CheckResultStatus enum. Set it through the language-appropriate helper:

Numeric valueString valueStatus
0"Success"Success
1"Warning"Warning
2"Critical"Critical
3"Failure"Failure (use sparingly — reserved for "the check itself could not run")

The bash and macOS wrappers ship set_result 0|1|2 as a convenience shortcut for assigning checkResult. Strings are case-insensitive when decoded.

PowerShell — $checkInput and $output

VariableTypeWhat it holds
$checkInputObjectDecoded JSON of the configured input parameters. Access with dot syntax: $checkInput.MaxAge. The wrapper deliberately avoids the name $input because that is a PowerShell automatic variable.
$outputHashtablePre-populated with checkResult = 0. Add fields with $output["key"] = value.

Anything you put on $output is serialised and shipped to octoja. Map your output keys to the Output fields you declared on the check definition; otherwise the dashboard renderer cannot bind them to UI blocks.

The wrapper also injects two SNMP helpers that shell out to the agent's bundled SNMP worker. Both take named parameters:

HelperSignaturePurpose
Get-SnmpGet-Snmp -TargetHost <host> -Oid <oid[]> [-Port 161] [-Version v2c|v3] [-Community public] [-User <u>] [-AuthProtocol <p>] [-AuthPassword <pw>] [-PrivProtocol <p>] [-PrivPassword <pw>] [-TimeoutMs 5000]SNMP GET against one or more OIDs; returns a hashtable of OID → value.
Get-SnmpWalkGet-SnmpWalk -TargetHost <host> -BaseOid <oid> [-Port 161] [-Version v2c|v3] [-Community public] [-User <u>] [-AuthProtocol <p>] [-AuthPassword <pw>] [-PrivProtocol <p>] [-PrivPassword <pw>] [-TimeoutMs 5000]SNMP WALK over a sub-tree; returns an ordered array of [pscustomobject]@{ Oid; Value }.

Note the parameter name: -TargetHost, not -Host. $Host is a PowerShell automatic variable, so the wrapper avoids it. Both helpers are only available when the agent exposes its SNMP worker via the OCTOJA_WORKER_BIN environment variable; if the variable is unset the helpers throw and your script should fall back gracefully.

Bash (Linux) helpers

HelperSignaturePurpose
get_inputget_input "key"Returns the input parameter as a string. Requires jq on the device.
set_outputset_output "key" valueSets an output field. Numeric / boolean / JSON-array / JSON-object values are detected by shape and emitted as their JSON type; everything else is escaped and emitted as a string.
set_resultset_result 0|1|2Shortcut for set_output "checkResult" <n> (0 = OK, 1 = Warning, 2 = Critical, 3 = Failure — same enum as CheckResultStatus).
snmp_getsnmp_get --host <host> [--port 161] [--version v2c|v3] [--community public] [--user <u>] [--auth-protocol <p>] [--auth-password <pw>] [--priv-protocol <p>] [--priv-password <pw>] <oid> [<oid>...]SNMP GET via the bundled worker. Prints the worker's JSON envelope to stdout; requires OCTOJA_WORKER_BIN and jq.
snmp_walksnmp_walk --host <host> [--port 161] [--version v2c|v3] [--community public] [--user <u>] [--auth-protocol <p>] [--auth-password <pw>] [--priv-protocol <p>] [--priv-password <pw>] <baseOid>SNMP WALK via the bundled worker. The base OID is the final positional argument; output is an ordered array of {Oid, Value}.

The SNMP helpers are flag-based (--host, --community, …), not positional. The output array is _OCTO_OUTPUT (associative). Use the helpers; touching the array directly is not part of the contract.

Bash (macOS) helpers

Same interface as Linux — get_input, set_output, set_result, and the same flag-based snmp_get / snmp_walk when OCTOJA_WORKER_BIN is set — but the implementation uses Python 3 instead of jq. A clean result envelope is still produced even if your script exits or crashes mid-run. If you do not provide a macOS-specific script, the Linux script is reused.

Input schema

Inputs are declared on the check (kind, label, default, required) and reach the script as a JSON object. Supported field kinds match the platform's InputFieldDefinition model — string, number, boolean, list, and so on. Read them via $checkInput.Key (PowerShell) or get_input "key" (bash / macOS).

Output schema

Outputs are declared on the check as a list of OutputFieldDefinition. Each entry has:

FieldTypeDescription
keystringProperty name as it appears in the JSON output (camelCase).
typeenumString, Number, Boolean, Array, or Object.
labellocalised stringDisplay label in the dashboard renderer.
unitstringOptional unit like %, ms, GB.
itemsrecursiveFor Array type — schema of each element.
propertieslistFor Object type — nested field definitions.

checkResult is implicit; declare additional fields you actually use in the dashboard.

Execution limits

LimitValueSource
Maximum runtime5 minutesFixed limit
Maximum decoded output size16 KB (default; configurable 1 KB – 1 MB)Instance setting
Status mappingFrom the checkResult field on the decoded JSON payload, not the script's exit codeFixed behaviour
Wrapper-stdout decodingPayload is extracted from between <<<OCTO_RESULT_BEGIN>>> and <<<OCTO_RESULT_END>>>, then base64-decoded; user stdout outside the markers is ignoredFixed behaviour

What breaks the check

PatternResult
Emitting your own <<<OCTO_RESULT_BEGIN>>> / <<<OCTO_RESULT_END>>> markersFailure — the parser extracts the wrong bytes; ordinary Write-Output / Write-Host / echo / ConvertTo-Json outside the markers is harmless
Missing or unparseable checkResult in the payloadFailure — the agent reports the parse error in place of a status
Runtime > 5 minutesFailure — the agent terminates the process
Decoded output > size capThe reported status is preserved, but the result is flagged Truncated = true and the Data payload is dropped; the dashboard renders the status with no detail fields
Linux device without jq installedFailure — the wrapper's output footer cannot serialize the result payload, so no valid result envelope is produced and the whole check fails

Diagnostic output during development goes to stderr — Write-Error / >&2 echo — and is captured by the agent without breaking the wrapper contract.

Encoding

Both check variants transfer and execute scripts as UTF-8 end to end. You do not need to convert anything or add a byte-order mark yourself — paste the script into the dashboard and octoja handles the encoding.

  • What octoja guarantees: the script is transferred character-for-character as UTF-8. On Windows it is executed from a script file that carries a UTF-8 byte-order mark, so both Windows PowerShell 5.1 and PowerShell 7 read it as UTF-8 — umlauts, °, and other non-ASCII characters arrive in the script exactly as you wrote them and are never reinterpreted in a legacy code page such as Windows-1252. On Linux and macOS the script is delivered as UTF-8 without a BOM, with Windows line endings normalised so the shebang stays valid.
  • Output: for the built-in Custom Script check the agent forces PowerShell's output encoding to UTF-8 before your script runs and captures stdout/stderr as UTF-8, so non-ASCII output such as °C or umlauts reaches the dashboard intact. For user-defined custom checks the result payload is serialised as UTF-8 JSON and travels Base64-encoded between the result markers, which makes it immune to output-encoding issues by construction.
  • What you still have to watch: octoja never substitutes characters — what you paste is exactly what PowerShell or bash parses. Editors, chat tools, and word processors like to replace straight quotes with typographic ones ( ) and - or -- with en/em dashes ( ). These look right but are different characters; depending on where they sit in the script they cause syntax errors or silently different behaviour. Replace typographic quotes and dashes with their plain ASCII counterparts before saving the check.

Scripts you bring in through the Check Importer can carry the same typographic characters from wherever they were copied — the same advice applies to imported scripts.