Skip to main content

Command reference

pathways <command> [options]

Every command runs on the project in the current folder, or the one -C <dir> (--project <dir>) names. pathways --help prints the usage, and pathways --version the version.

The commands that read or change stored messages (start, status, messages, replay, clear, retention, audit, rekey, lease, and verify unless it is given --samples) open the database as start does, so they need the same pathways.yaml, encryption keys and environment. None of them prints a value you typed back to you: an argument can hold a path or a value from a message, and standard error often ends up in a log.

pathways init​

pathways init [dir]

Writes the starter project into dir (default: the project folder), which must be empty or not exist yet: pathways.yaml, the adt-to-lab channel with its transform and tests, a .gitignore, a package.json, a Dockerfile and .dockerignore that build the project's own image (see Deploy the image), and a development master key generated for this project in .pathways/dev.key. On Linux and macOS the key is readable by its owner only (mode 0600, in a folder with mode 0700). On Windows a file's mode does not limit who can read it, so there the project folder's access control list is what keeps the key private, and init says so: keep the folder to the accounts that run Pathways. Nothing is written if the folder is not empty.

pathways start​

pathways start [--once | --standby] [--no-migrate] [--dry-run]

Runs every channel, and the health endpoint and the probe listener if pathways.yaml configures them, until it receives SIGINT or SIGTERM. Retention runs after the first pass and then every retentionInterval. Needs encryption in pathways.yaml.

On either signal it stops cleanly and exits 0: /readyz answers 503 draining at once, sources stop taking messages, what is being stored is answered, and each lifecycle hook gets up to its time limit to finish the events queued for it. Windows has no SIGTERM. There Ctrl+C in the engine's console is SIGINT and stops it the same way, while ending the process (Task Manager, taskkill /F, or a service manager that gives up waiting) stops it at once, with no stop at all. A hard stop loses no message: each one was stored before its source acknowledged it, and a file the engine had claimed is stored on the next start under the same key, so it is stored once. Only the hook events still queued are lost.

To run it as a Windows service, use a service wrapper that stops a console program with Ctrl+C before it ends the process, and give it a stop timeout longer than your longest hook timeout, so the hooks finish what is queued.

It first takes the engine lease, so a second engine on the same database is refused and exits 1, naming the holder. An engine that loses its lease stops sending and taking in at once and exits 1. See one engine per database.

Before that, it reads the database's schema: it applies the expand steps it has and the database lacks, runs on a schema a newer Pathways moved forward with expand steps, and refuses, naming both versions, a schema a newer Pathways contracted past this one. See schema changes.

Once running it listens on a control socket for pathways channel, in a folder only its user can open; a start that finds another engine answering there for the same project refuses and exits 1. See pause, stop and reload a channel.

OptionMeaning
--oncetake in, transform and deliver what is waiting, then exit; no health endpoint, no probe listener, no retention
--standbyhold no lease and open no source until the engine holding the lease stops, then take over; health answers 503 standby until then. Needs a database server, not SQLite
--no-migrateapply no schema step; refuse, naming both versions, a database that lacks an expand step this version needs. For a database pathways migrate keeps up to date
--dry-runrun every destination dry, as dryRun: true does for one: each output is transformed and recorded, its delivery marked dry-run, and nothing is sent; no destination's connection is ever opened. Every pending delivery of a destination that runs dry is marked dry-run, those queued before it too

pathways test​

pathways test [name...] [--update]

Runs each channel's tests/ inputs through its transform and compares the output with the saved .expected files, for every channel or the ones named. Needs no keys and stores nothing.

OptionMeaning
--updatesave the current outputs as the expected ones

pathways verify​

pathways verify [--channel <name>] [--since <time> | --ids <id,...> | --samples <dir>] [--json]

Runs inputs through this project's transforms, in the same workers the engine uses, and compares each output with the one recorded for the same input: the outputs stored for a message, or a sample's .expected file. Point -C at a candidate project, such as a copy on a newer Pathways, to see what an upgrade or a changed transform would send differently. See Upgrade Pathways.

For each channel and destination it prints how many inputs were the same, different, or failed, and for each one that was not the same, where it first differs or why it failed:

WhereFormat
PID[1]-5.2HL7 v2: the segment and its occurrence, the field, then the repetition ([n]), component and subcomponent where either side has more than one
/name/0/familyFHIR and JSON: a JSON pointer, which names the keys on its path. For json, a key can be data the message carries, such as a medical record number
byte offset 118any other format, or when the outputs read as the same value written differently
2 outputs from the transform, 1 output recordedthe number of outputs differs

It never prints a value. Stored messages are read through encryption at rest, decrypted in memory only, and each one is written to the audit log (verify) before its content is read; nothing is written to the message or delivery tables. A message with no recorded outputs (received, failed or cleared) is skipped. Two outputs are the same only when they are the same bytes.

It opens the database as start --no-migrate does: it applies no schema step, and refuses, naming both versions, a database that lacks a step the candidate's Pathways needs. The candidate's transforms run with this process's environment, as the engine's do, and what they write to the console goes to its output. Each transform reads its channel's config: as the engine does, and a stored message runs with its own id as ctx.messageId and no source facts, as a replay does; a transform that reads ctx.source is therefore reported different on a message whose recorded outputs came from its first run, which had them. ctx.attempt is the message's generation plus one, and after replay --resend, which raises the generation without a transform run, that is not the attempt that made the recorded outputs, so a transform that reads it is reported different there. A sample runs with the context pathways test gives. On SQLite, where the database file does not exist, it stops and creates none. It builds no destination, so it sends no message; pathways.yaml's engine-wide hooks receive its audit entries, as they do every command's.

OptionMeaning
--channel <name>only this channel
--since <time>the messages stored from this time, newest first, in the forms messages takes; the default is 24h
--ids <id,...>these stored messages, in the order given
--samples <dir>instead of stored messages, the inputs in <dir>/<channel>/, each against its .expected file as in tests/; opens no database

It exits 1 when an input was different or failed, and 4 when a message named does not exist.

pathways validate​

pathways validate [--print]

Checks pathways.yaml and every channel.yaml as start does before it reads a secret, opens the database or listens: every key and the rules between keys, that the transform, module, hook and ACK builder files they name exist, and that pathways.yaml sets encryption. A problem is printed as start prints it, and the exit code is 3; a valid project prints how many channels it has and exits 0. It reads no secret, TLS file or database, so it runs in a project's CI without them; start still checks those when it runs. A setting written as { env: NAME } is read from the environment as start reads it, so an unset variable with no default is a problem here too. See Check configuration in an editor and in CI.

OptionMeaning
--printprint the configuration as start reads it, as one JSON document, in place of the summary: every default filled in, every path absolute, every setting from the environment resolved and listed under environment with its variable. Secrets stay the references start reads them from, and the query string of a URL read from the environment shows as ?(hidden)

pathways secrets check​

pathways secrets check [--json]

Loads the project as validate does, then resolves every secret start reads: each { env: NAME }, { file: path } and { secret: { provider, name } } reference, each provider module's once per secret, under the provider's timeout. A destination that runs dry (dryRun: true) is left out, as start leaves it. Then it builds the key provider as start does and wraps and unwraps a throwaway data key with it, so a key that cannot be had or a key service that refuses shows now, not at the first message. It prints one line per reference: where it is named, the provider, the secret's name (left out for a file, whose name is a path), and the version, identity and expiry the provider answered with. It never prints a value, its length or any part of it. It opens no database and listens on nothing, so it runs as a pre-deploy step with the deployment's own environment and credentials. It exits 0 when every secret resolved and the round trip passed, 1 when any did not, and 3 when the configuration is invalid. See Read secrets from files and secret stores.

OptionMeaning
--jsonprint one JSON document: ok, references (each with where, provider, name, field, requestedVersion, status, version, identity, expiresAt and problem) and keyProvider (kind, status, keyId and problems)

pathways status​

pathways status [name...] [--json]

Each channel's state, counts, retrying deliveries and failed messages, from the database.

pathways messages​

pathways messages [--channel <name>] [--status <state>] [--since <time>] [--until <time>] [--limit <n>] [--before <id>] [--json]
pathways messages --channel <name> --where <key=value> [--where <key=value>...] [--status <state>] [--since <time>] [--until <time>] [--limit <n>] [--before <id>] [--json]
pathways messages show <id> [--content] [--json]

Lists stored messages, newest first, with ids, states, times, sizes and duplicate flags, never content; or shows one message and its deliveries. Each search and view is audited.

OptionMeaning
--channel <name>only this channel
--status <state>only this state: received, queued, retrying, delivered, dry-run, filtered, failed or cleared
--since <time>from this time: 2026-11-07, 2026-11-07T12:00:00Z (UTC unless a zone is given), or a duration ago such as 24h or 7d
--until <time>before this time, in the same forms
--limit <n>at most n rows, 1 to 1,000; default 50
--before <id>the next page, as the last line of the previous one says
--where <key=value>only messages whose ctx.meta key holds the value, as text; the key must be in the channel's index: block and --channel is needed; repeat it to need every pair. See Search messages by metadata
--contentshow only: print the content, which is PHI; the view is audited first

pathways replay​

pathways replay <id...> [--resend] [--json]

Runs each message through its channel's transform as it is now and queues fresh deliveries behind those waiting. Refused for a message not yet transformed, with deliveries pending, or whose channel is gone. Each attempt is audited.

OptionMeaning
--resendsend the stored outputs again, without transforming

pathways clear​

pathways clear <id...> [--json]

Marks failed messages, and their deliveries that failed for good, cleared, so they leave the failed count and retention can delete them. Each is audited.

pathways retention​

pathways retention [--dry-run] [--json]

Deletes delivered, filtered and cleared messages past their channel's retention period, with their deliveries. A real run is audited.

OptionMeaning
--dry-runsay what would be deleted; delete nothing

pathways audit​

pathways audit [--action <name>] [--message <id>] [--channel <name>] [--since <time>] [--until <time>] [--limit <n>] [--before <id>] [--json]

Reads the audit log, newest first. --action takes search, view, content-view, replay, resend, clear, retention, config, lease-release, migrate, channel-pause, channel-resume, channel-stop, channel-halt, channel-reload or verify; --message takes a message id; the other options are as for messages.

pathways rekey​

pathways rekey [--encrypt-plaintext]

Re-encrypts every stored body, error text and message metadata under the current master key, so older keys can be retired, and derives each message's metadata index tags again under the current key and the channels' index: blocks. Prints how many values it re-encrypted, found already current, encrypted from plaintext, left unencrypted, could not read, and skipped because they changed while it ran, and, when any, how many messages' index tags it derived again. Safe beside a running engine. On SQLite, rekey then empties the write-ahead log into the database file, and exits 1 if a read still under way after five seconds keeps it from doing so; once it exits 0, an older key can go. On a database server or a custom store, rekey also says on stderr that the store may still hold earlier versions of the rewritten rows under the older keys: keep each until the server's logs, free space, replicas and backups no longer hold them. On any store, a copy or backup made before rekey still holds every value under the older keys: keep each key while such a copy may be restored, since restoring one once its key is gone loses every stored message in it. Needs encryption with keys or a provider, and, for a custom store, one that also implements ContentStore.

OptionMeaning
--encrypt-plaintextalso encrypt values stored while encryption was none

pathways lease​

pathways lease status [--json]
pathways lease release --force [--json]

lease status prints who holds the engine lease: its node id, host, since when, epoch, and the time it has left, by the database's clock. lease release --force frees the lease of an engine whose host is gone, so the next start takes it at once instead of after it lapses, and writes the release to the audit log (lease-release); it exits 1 when the lease is already free. An engine still running under a lease freed this way stops at its next renewal.

pathways probe​

pathways probe [--ready] [--if-configured]

Asks the probe listener of the engine running in this project (the probes block of pathways.yaml) for /livez, or /readyz with --ready, prints the word it answers, and exits 0 on a 200 and 1 on anything else. It exits 1, saying why, when pathways.yaml has no probes block, when the configuration is invalid, or when nothing answers within 5 seconds, so a container's health check sees only 0 or 1. With --if-configured, no probes block exits 0 and prints not-configured instead; the Docker image's health check runs it that way. It reads pathways.yaml and opens no database. A listener on 0.0.0.0 or :: is asked on the loopback address. It reads at most 256 characters of an answer.

OptionMeaning
--readyask /readyz in place of /livez
--if-configuredexit 0, asking nothing, when pathways.yaml has no probes block

pathways migrate​

pathways migrate [--contract] [--dry-run] [--json]

Applies the schema steps this version has and the database lacks, under the migration lock, and prints each with its generation and its tag:

Schema generation 4, last changed by Pathways 0.0.1; this is Pathways 0.0.2.
Applied 5 steps:
5 0005-send-markers expand
6 0006-leases expand
7 0007-schema-info expand
8 0008-content-drop expand
9 0009-channel-states expand
Now generation 9.

Expand steps are always applied. Contract steps wait for --contract, and with it are still refused while the schema changed within migrations.rollbackWindow. A schema a newer Pathways moved forward is left as it is. It needs no encryption keys, and a custom store, which keeps its own schema, is refused. It does not see which versions other engines on the database run, so stop or upgrade them before --contract. Every run is written to the audit log (migrate), a dry run and a refusal too, once the database has an audit log; a dry run on an empty database says it has none yet. It exits 1 when it refuses or a step fails. See upgrade Pathways.

OptionMeaning
--contractapply contract steps too, unless the schema changed within migrations.rollbackWindow
--dry-runsay which steps would be applied; apply none

pathways channel​

pathways channel pause <name> [--json]
pathways channel resume <name> [--json]
pathways channel stop <name> [--deadline <time>] [--json]
pathways channel halt <name> [--deadline <time>] [--json]
pathways channel reload <name> [--json]

Asks the engine running this project on this machine, through its control socket, to change one channel. pause stops the source taking messages in, while stored messages go on through the transform and delivery; resume runs the channel again; stop pauses it, ends its transforming and delivering after the message in hand, waits up to --deadline (30 seconds by default) and says what it left; halt does the same but cuts off the delivery in flight now; reload loads the channel's transform and settings anew and swaps them in between messages. A paused or stopped channel stays so across a restart, and on every engine on the same database. Each command is written to the audit log (channel-pause and the like) with the operating system user who ran it. When no engine answers, it says engine not running and exits 1. See pause, stop and reload a channel.

OptionMeaning
--deadline <time>stop, halt: how long to wait for the work in flight, such as 30s, at most 24d
--jsonprint the engine's report as one JSON document

Exit codes​

CodeMeaning
0done
1a test failed; the engine, a command or an extension module could not start or stopped on an error; init could not write; rekey left a value not under the current key, or on SQLite a read under way kept it from emptying the write-ahead log; a replay, resend or clear was refused, or the message failed again; another engine holds the lease, the engine lost its lease, or there was no lease to release; the database schema is one this version will not run on or migrate; verify found a difference or a failure; secrets check found a secret or the key provider that could not be had; probe found the engine not alive or not ready; no engine runs the project, or a channel command was refused
2the command line was not understood; nothing was done
3the configuration is invalid; nothing was done
4a message named does not exist; any others named were still acted on