Skip to main content
Version: v5.6.1

opschain runner

Included from Basic tier

Runs an OpsChain runner in the foreground against a server: on the first run it registers with the --token registration token, then serves the long-poll work loop — acquiring queued steps, executing them with its advertised executors, and streaming results back. The server address resolves through the global target precedence — --server <url>, OPSCHAIN_SERVER, or the active context — the same way every other server-facing command finds its server.

On start it prints the executor set it advertises: advertising executors: hostexec,containerexec.

Flags​

FlagMeaning
--server <url>[,<url>…]Server base URL (global flag; default: OPSCHAIN_SERVER or the active context). A trailing / is stripped. For the runner it accepts an ordered comma-separated list — the primary first, failover targets after; see "Multiple servers" below.
--token <token>Registration token — required on the first run; afterwards the saved identity in --dir is used.
--name <name>Runner display name (default: hostname).
--executors auto|<list>Executor kinds to advertise: auto (hostexec, plus containerexec when a container runtime is detected) or an explicit comma-separated list (hostexec,containerexec — naming containerexec fails when no runtime is reachable).
--labels <list>Comma-separated runner labels, matched by step placement.
--dir <path>Runner state directory (default: <opschain home>/runner).
--env-passthrough <list>Comma-separated extra environment variable names handed from the runner's own env to every step (default: $OPSCHAIN_RUNNER_ENV_PASSTHROUGH). A passed-through OPSCHAIN_VAULT_TOKEN reaches the step as a step-scoped child token minted at dispatch, never as the runner's own value.
--spool-max-mb <n>Per-step disk log spool budget in MB; over budget the oldest chunks are dropped under a gap marker (default 64).
--max-parallel-steps <n>Declared step capacity: the most steps of one pinned run this runner executes at once, clamping the run's runner.max_parallel_steps setting to min(offered, declared). Size it as N × per-step peak RSS ≤ the host/container memory. Default: $OPSCHAIN_RUNNER_MAX_PARALLEL_STEPS, else unlimited — follow the server's cap. When the clamp binds, the run's step log carries one notice naming both numbers.

Environment​

VariableMeaning
OPSCHAIN_RUNNER_ENV_PASSTHROUGHDefault for --env-passthrough.
OPSCHAIN_RUNNER_MAX_PARALLEL_STEPSDefault for --max-parallel-steps (a positive integer; anything else reads as unset).
OPSCHAIN_RUNNER_RENEW_INTERVALLease-renewal interval override (a Go duration, e.g. 30s).
OPSCHAIN_RUNNER_POLL_TIMEOUTLong-poll timeout override (a Go duration).

Multiple servers (ordered failover list)​

--server https://site-a.example,https://site-b.example (or the same in OPSCHAIN_SERVER) registers the runner with an ordered server list: the first entry is the primary, later entries are failover targets. The list is recorded in the saved identity (runner.json gains server_urls; an identity saved before the field keeps working as a one-server list).

  • Failover happens on sustained unreachability — three consecutive transport failures to the current server (a refused or dropped connection, or a load balancer's own error page because no backend is left) — and the runner prints server unreachable after 3 consecutive attempts: failing over to <url>. A server that answers unavailable (a replica whose log-ingest owner has not been adopted yet after a peer died, the system-quiesce write barrier, a replica whose database is unreachable — store unavailable: …) has answered: the call is retried on the same server and never counts toward failover. Through such an answer the in-flight job keeps running and renewing, its log chunks stay spooled until they land, and its result is re-submitted for up to ten minutes before the five-attempt budget applies — a database outage shorter than the lease TTL costs nothing. A chunk the server refuses by verdict instead leaves the spool with log chunk N dropped: the server refused it (…); the gap is reported to the server in the runner's log: a gap record (the sequence and a reason class, never the content) takes the chunk's place in the spool and is uploaded and retried like a chunk, so the step's log shows *** log gap: 1 chunk(s) N..N not delivered — <reason> *** where the line would have been and the run records warn:logs:chunks_dropped. The same happens for a spool entry that cannot be read back (spool_unreadable); chunks still spooled when the end-of-step flush times out are reported on the result submission (seal_timeout), and a chunk the server answers failed_precondition for arrived after the attempt's log sealed — the server marked that gap itself. An in-flight job survives the switch: its lease renewals, log chunks and result submission continue against the next server (every replica honours the job's token through the shared database).
  • Failback is handshake-governed: every session handshake tries the primary first, so the runner returns to it on its next natural re-handshake after the primary recovers (server failover: now using <url>).
  • Changing the list re-registers, exactly like changing the server: a saved identity whose list differs from the configured one refuses with runner is registered with <list>; remove <dir> to re-register.
  • A server that ANSWERS — even with a refusal — is never failed over: it speaks for the shared estate, so its verdict stands.

Signals​

SIGTERM or SIGINT drains gracefully: the runner finishes the in-flight job and stops acquiring, printing draining: finishing the in-flight job, no new work (signal again to stop hard). A second signal stops hard.

Once the in-flight job has finished (at once when idle) the runner prints drained: no longer acquiring jobs, announces its exit to the server and prints goodbye: the server marked this runner stopped — the fleet view reads it stopped immediately and an info:runner:goodbye event is recorded. A goodbye the server did not answer within 2 s prints goodbye not delivered (the server will age this runner out) instead. With --log-file, these lines land in the log file (the runner's operational output) while the draining: acknowledgement and the start-up advertising executors: line stay on stderr — read the log file for the stop narrative.

Exit codes​

CodeMeaning
0Clean shutdown.
1Registration or the work loop failed (an unreachable container runtime named in --executors included).
2Usage error (the runner needs a server target — pass --server <url>, set OPSCHAIN_SERVER, or 'opschain context use <name>').

Examples​

First run:

opschain runner --server https://opschain.example.com --token <registration-token> --labels linux,build

Host-only execution with a bigger log spool:

opschain runner --server https://opschain.example.com --executors hostexec --spool-max-mb 256

See also​

  • opschain docs fleet-queue — how queued steps reach runners and why one might not dispatch.
  • opschain learn 6.1 — runners and the fleet.