Check status and health
From the command line
npx pathways status
npx pathways status adt-to-lab --json
status reads the database, so it works whether or not the engine is running. For each channel it
prints its state, its message and delivery counts, up to 10 deliveries waiting for a retry, and up to
10 failed messages:
adt-to-lab: retrying
messages: 0 received, 41 transformed, 3 filtered, 1 failed, 0 cleared
deliveries: 2 pending (1 retrying), 39 delivered, 0 failed, 0 cleared
retrying: delivery 40 (message 41, destination 0), 3 attempts, next try 2026-11-10T07:52:16.000Z
failed: message 17, since 2026-11-10T07:31:02.114Z
A channel's state is retrying while a delivery waits to be tried again, otherwise failed while it
holds a failed message or delivery, otherwise ok. A channel that has stored messages but is no longer
in the project is listed as (not configured).
Serve a health endpoint
pathways start serves a health endpoint when pathways.yaml has a health: block. It is
authenticated like an HTTP source: a bearer token by default (from PATHWAYS_HEALTH_TOKEN unless
auth names another variable), or mutual TLS with tls.ca.
health:
port: 9090
tls:
cert: certs/health.pem
key: certs/health.key
curl --cacert certs/ca.pem -H "Authorization: Bearer $PATHWAYS_HEALTH_TOKEN" https://localhost:9090/
A GET or HEAD on any path but /livez and /readyz answers with JSON:
{
"status": "ok",
"storage": "ok",
"version": "0.0.1",
"schemaGeneration": 7,
"node": "3f9a1c2b7d4e5f60",
"role": "active",
"leaseEpoch": 3,
"draining": false,
"channels": {
"adt-to-lab": {
"status": "retrying",
"source": "up",
"state": "running",
"messages": { "received": 0, "transformed": 41, "filtered": 3, "failed": 1, "cleared": 0 },
"deliveries": { "pending": 2, "delivered": 39, "failed": 0, "cleared": 0, "retrying": 1 }
}
}
}
Beside the status and the channels, the body says which engine answered: version, the version of
@cosyte/pathways it runs; schemaGeneration, the
schema generation that
version brings the database to, its number of schema steps (null with a custom store; the database
itself can be at a later generation a newer Pathways moved it to); node, the node id it holds the
engine lease under; role, active or standby, or single on a custom store without leases; leaseEpoch, the
engine lease's epoch while it holds it, else null; and draining, true once it was told to stop
and is closing its sources.
The answer is 503, with status: "unavailable", only when the engine itself is broken: the database
cannot be read (storage: "unreachable", and each channel's status unknown), or a source is down
(its source: "down"): it cannot listen, and the engine keeps trying it, or it said it lost its
connection (a broker, a remote server or a socket it dialled), until it says it has it again. A destination that is down shows as a
channel retrying, and the answer stays 200, because restarting the engine would not bring the
destination back. Point a restart policy at the status code, and an alert at the channels.
Each channel's state is running, or paused or stopped when an operator set it so with
pathways channel (see pause, stop and reload a channel).
Neither is a fault: the answer stays 200, and the source of a paused channel shows up.
A standby (pathways start --standby) answers 503 with status: "standby" and no channels until it
takes the engine lease, so a readiness check routes nothing to it; point a liveness check at it only
if a standby that answers at all counts as alive. See
a standby.
A channel's deliveries also counts dry-run once one of its destinations has
run dry. The body holds states and counts, never content. pathways start --once does not serve the endpoint.
/livez and /readyz on the health endpoint answer as the probe listener below does, behind the
same token.
Probe liveness and readiness
A load balancer's health check or an orchestrator's probe often cannot send a token, so a probes
block in pathways.yaml opens a second listener that needs none. It answers two paths, each with a
status code and one word, and nothing else: no channel name, no count, nothing stored.
probes:
port: 9091
plaintext: true
It listens on 127.0.0.1 unless host says otherwise; inside a container, set host: 0.0.0.0 for
a probe from outside it to reach it. It serves plain HTTP only, which plaintext: true says: it
answers one word and takes nothing in.
| Path | 200 | 503 |
|---|---|---|
/livez | live | stalled: the engine loop has made no step within its bound |
/readyz | ready | starting, standby, draining, fenced (the engine lost its lease), or unavailable: storage cannot be read, or a source is down |
Every other path answers 404, and every method but GET and HEAD 405.
Liveness looks at the engine loop alone. A step is a message taken in by a polled source, one
transform, one delivery attempt, a channel's turn in a pass, or one channel of a retention run. The bound is 60
seconds, plus the poll interval, plus the longest time one step can take: the longest of each
channel's transform timeout and each destination's
delivery deadline (an HTTP
destination's timeout plus a second, twice an MLLP destination's ackTimeout plus a second, and a
file or module destination's timeout, 30 seconds by default). A polled source's pass runs beside
the loop and is cut off at its own deadline, so it holds no step up. A source that is down or a
destination that is down never makes the engine stalled, since the loop keeps stepping while it
retries them and a restart would not bring them back; a standby, and an engine still starting or
stopping, are always live. One channel's retention delete has no time limit: a channel with a very large
backlog past its retention period (the first run after a long outage, or after retention was
shortened) can take longer than the bound to delete, and /livez answers stalled until it is
done. Run pathways retention by hand first in that case. Point a restart policy at /livez.
Readiness is 503 until the engine is running, and while it runs when storage cannot be read
(read at most once a second, and counted unreachable after 5 seconds without an answer) or a source
is down. On SIGTERM or SIGINT it turns 503 draining at once, while the sources are still
closing, and the listener closes last. Point a load balancer or a readiness probe at /readyz.
curl http://127.0.0.1:9091/readyz
pathways probe asks the same listener from the command line, and exits 0 or 1: pathways probe
for liveness, pathways probe --ready for readiness. The Docker image's health check runs it; see
Run Pathways in Docker.
Logs
The engine writes one JSON line per step to standard output, through pino: received,
transformed, filtered, transform-failed, delivered, delivery-retry, delivery-rejected and
more, each with the channel, ids and counts. Logs, errors and codes lists them.
Ship them with whatever collects your standard output.
Related
- Inspect, replay and clear messages.
- Add lifecycle hooks and webhooks, to be told rather than to ask.
- Configuration reference: the
probesblock.