Skip to content

chant run

chant run <name> [flags]
chant run list [flags]
chant run status <name> [flags]
chant run approve <op> <gate> [flags]
chant run cancel <name> [flags]
chant run log <name> [flags]
chant run --components <name|all> [flags]

chant run runs Op workflows — named, phased workflows defined in *.op.ts files — and, with --components, component deploy compositions.

chant run <name> runs local by default: the Op executes in-process, with nothing installed and nothing running. Phase sequencing, retries, activity profiles, gates and onFailure compensation all work there.

Every run subcommand addresses one runtime, the thing that actually hosts the run. Without a flag that is the built-in local runtime, which executes the Op in the chant process. --on <lexicon> hands the run to a configured lexicon that implements the opRuntime hook instead, and the subcommands follow it there: run status, run log, run list, run cancel and run approve all query the runtime named by the same flag.

Terminal window
chant run alb-deploy # the built-in local runtime
chant run alb-deploy --on fountain # a lexicon that hosts runs
chant run status alb-deploy --on fountain

A name that is not a configured lexicon is refused with the list of lexicons that are. A configured lexicon that hosts nothing is refused by name. Core ships only the local runtime and never imports a hosting lexicon, so a project gets the hosted path by declaring the lexicon, not by upgrading chant.

The fountain lexicon hosts a run on a teammate’s thread. chant run <op> --on fountain posts the command line chant run <op> to the steward as a prompt, tails the conversation’s event stream, and exits with the status the run reached. The Op executes in fountain’s sandbox, so the run record lands on the ledger from there rather than from this process.

Terminal window
chant run alb-deploy --on fountain
chant run alb-deploy --on fountain --progress-json
chant run status alb-deploy --on fountain
chant run log alb-deploy --on fountain --limit 10
chant run cancel alb-deploy --force --on fountain

The endpoint and the token come from fountain.profiles in chant.config.ts, and so does the steward: a profile’s team names the teammate the prompt goes to. Without one, the Op’s labels.Agent or a --param agent=<name> binding opens a fresh conversation on that agent instead. An Op with none of the three is refused with all three named.

A teammate runs one turn at a time. A prompt sent while another op is running comes back as conversation_busy, and chant reports it with the conversation’s address rather than queueing behind a turn whose length it cannot predict. Wait for the thread to go idle and run the command again.

Tool calls on the stream become phases, so --progress-json and the human renderer show the run’s shape as it happens. Fountain closes an idle stream connection after 60 seconds, which is not the end of a turn: the reader reconnects with Last-Event-ID and the server replays what arrived meanwhile. Only real silence ends the wait, after 30 minutes, and FOUNTAIN_STREAM_IDLE_TIMEOUT (in seconds) widens that for a run that thinks for longer.

chant run approve <op> <gate> --on fountain writes the resolution to the gate ledger as always. It then posts chant run <op> to the steward, with --approver and --url on it. The sandbox re-runs the op and walks through the gate, reading the resolution back off the ledger. --durable-requests is reserved for answering fountain’s own pending request instead; the endpoint it needs has not shipped, and the flag currently refuses by naming the upstream issue rather than reporting a resolution it did not make.

Run an Op, rendering each phase and step as it runs.

Terminal window
chant run alb-deploy # the local runtime (default)
chant run alb-deploy --json # the run's ledger record on stdout
chant run alb-deploy --on fountain

A gate in the Op stops the run pending a resolution on the gate ledger. The run prints the gate’s description and the chant approve line, and exits 3 — deliberately not 1, so a CI job can report “waiting on approval” rather than a failure. No phase after the gate runs, and no onFailure phase runs either: nothing failed, so there is nothing to compensate for. See Gate-as-fact.

GitHub Actions has no neutral conclusion for a run: step, so a push-to-main apply that stops at its gate is a failed workflow run until someone approves. --gated-exit <code> maps that one outcome to a code of your choosing:

Terminal window
chant run prod-apply --gated-exit 0 # a pending approval is not a broken build

Only the gated outcome is remapped. A run that fails still exits 1 and a run that completes still exits 0, so the flag can never hide a broken apply. The --json payload is untouched by it: the record still says "status": "gated" and still carries the gate and the exact chant approve line, so a job that maps the code to success can still tell the two apart and report the pending gate. The value must be a process exit status (0-255); anything else is refused before the Op runs.

Whenever a run is gated, chant run also appends a short markdown block to the file named by GITHUB_STEP_SUMMARY, when that variable is set. The block names the Op and the gate, gives the exact chant approve command, and points at the _gates/<op>.jsonl ledger the pending fact was written to. GitHub Actions, Forgejo Actions and Gitea all set it, and any other CI that does gets the same block. Nothing is written when the variable is absent, and a write that fails never changes the run’s outcome.

The github Op generator passes --gated-exit 0 on push-triggered jobs and adds a follow-up job that posts the pending gate on the merged pull request, so a gated apply is a green run with a visible pending state rather than a red one.

Every run appends one record to the run ledger on the chant/lifecycle branch, at <env>/runs__<name>.jsonl beside the converge ledger. The record carries the run id, the start and end instants, the status (ok, fail), the Op’s labels, every outcomeAttribute the run captured, and each phase’s per-step status. --json prints that same record, so what you read on stdout and what chant operator log reads back later are one document. <env> is the Op’s own labels.Env, or local when it declares none.

List all discovered Ops (*.op.ts files) with their declared labels and the run state the selected runtime reports for each.

Terminal window
chant run list
chant run list --on fountain

Output columns: NAME, PHASES, DEPENDS, OVERVIEW. The name carries the run state when the runtime reports one. With --components, the command points at chant list --components instead, which is where a component’s declared shape lives.

Show the state the selected runtime reports for an Op’s latest run. A runtime with no record of the Op says so and exits 0.

Terminal window
chant run status alb-deploy
chant run status alb-deploy --on fountain

A Gate line appears when the latest run stopped at one, naming the gate and how long it has been pending.

Record a gate’s resolution, then wake the runtime hosting the gated run. This is the rename of run signal, and a different act: a resolution is a fact on the gate ledger, not a message to a waiting workflow. The ledger write is the same one chant approve performs, so a resolution recorded either way reads back identically; the runtime is told afterwards only so a parked run wakes now rather than on its next tick. A runtime that has no wake hook is not an error, since the next run reads the fact.

Terminal window
chant run approve alb-deploy release-approval --approver alex
chant run approve alb-deploy release-approval --on fountain --url https://github.com/acme/infra/pull/42

--approver <name> records who approved, defaulting to GITHUB_ACTOR, GITLAB_USER_LOGIN, or USER. The address the approval happened at goes in --url, which must be an absolute http/https URL. Free-text context goes in --note.

Cancel the active run on the selected runtime. Requires --force. The local runtime runs an Op in the foreground and so has nothing to cancel; Ctrl-C is the answer there, and the command says so.

Terminal window
chant run cancel alb-deploy --force --on fountain

On a persistent steward, --force terminates the conversation and takes its sandbox with it, so the checkout and the tool cache go too.

Show the run history the selected runtime holds for an Op, newest first.

Terminal window
chant run log alb-deploy
chant run log alb-deploy --on fountain --limit 10

Run a discovered component’s deploy composition on the same in-process executor an Op uses. all runs every discovered component in dependency order (parallel-safe waves); a single name runs just that component.

Terminal window
chant run --components search-service --env staging
chant run --components all --env production

A gate anywhere in a component’s composition stops that component pending chant approve <component> <gate>, exit 3, with no saga unwind and no rollback phase. The steps that ran before the gate are the steps the author meant to run before the approval.

--param name=value and --params-file <path> bind declared build-time parameters for the component build, with the same precedence as chant build. --sandbox runs any project source the driver has to execute in one isolated child process; an Op with a policyGate step is refused under --sandbox, since that gate builds the project in the chant process.

Cross-job outputs: --dump-outputs and --seed-outputs

Section titled “Cross-job outputs: --dump-outputs and --seed-outputs”

A single-component run in one CI job can reference a component that ran in an earlier job. --dump-outputs <file> writes the accumulated cross-component and cross-stack outputs after the run as JSON keyed by component name. It is written even when the run fails, so a resumed run still has what completed. --seed-outputs <file> (repeatable) loads such files before the run, so a stackOutput() or @<dep>.publish.* reference to an upstream component resolves.

Terminal window
chant run --components shared-alb --env staging --dump-outputs out/shared-alb.json
chant run --components search-service --env staging --seed-outputs out/shared-alb.json

--progress-json streams one NDJSON record per line to stdout while the run executes, so a consumer can render live progress instead of tailing logs. On the component path each line is a RunProgressEvent (wave, component, phase, step). On chant run <name> each line is a StepRecord for a settled step, fed by whatever runtime is hosting the run. Run semantics, ordering and exit code are unchanged.

On a successful run, chant run --components auto-emits one immutable release record per component that published a digest-bearing artifact — (component, env, digest, gitSha, runId, timestamp, actor), appended to the same release ledger the standalone chant components release command writes to. This is a post-run CLI step, never a step of the composition itself, and it never runs at all when the deploy fails. See Auto-recorded releases below.

Auto-recording is on by default — no flag needed. It writes exactly one record per component whose deploy composition actually published a digest-bearing artifact (via publish-image, publish-artifact, or load-image-on-host); a component with no publish step, or a failed deploy, writes nothing. A ledger-write failure (e.g. a concurrent push, or no git remote configured) is reported as a warning and never turns an otherwise-successful deploy into a nonzero exit.

Opt out per-invocation with --no-release-record, or project-wide via chant.config.ts:

Terminal window
chant run --components search-service --env staging --no-release-record
chant.config.ts
export default {
release: { autoRecord: false },
};

The --no-release-record flag always wins over config for that one invocation. See Observability for the release ledger this feeds, and chant components status to query it.

FlagDescription
--localRun with the local in-process executor — the default (run <name>, run --components)
--on <lexicon>Which runtime hosts the run: a configured lexicon with an opRuntime, or the built-in local runtime when omitted (every run subcommand)
--componentsTarget a component instead of an Op (run --components)
--no-release-recordSkip auto-emitting a release-ledger record after a successful run --components deploy — default is ON (run --components)
--env <name>Target environment, threaded into every capability’s context (run --components); defaults to local
--jsonEmit the run’s ledger record as JSON on stdout (run <name>); the structured run result for run --components
--gated-exit <code>Exit code for a run that stopped at an unapproved gate, instead of the default 3. Only that outcome is remapped; a failure is still 1
--forceRequired for run cancel
--progress-jsonStream one NDJSON progress record per line to stdout while the run executes
--dump-outputs <file>After the run, write the accumulated cross-component outputs as JSON to file (run --components)
--seed-outputs <file>Before the run, load outputs written by --dump-outputs; repeatable (run --components)
--param <name=value>Bind a declared build-time parameter for the component build; repeatable (run --components)
--params-file <path>JSON file of build-time parameter values (run --components)
--sandboxExecute project source in one isolated child process. Refuses an Op with a policyGate step
--approver <name>Who approved a gated change; recorded on the gate-resolution fact (approve, run approve)
-p, --profile <name>Named connection profile the hosting runtime targets, such as a fountain.profiles entry; each runtime’s own default when omitted
--durable-requestsReserved. It would resolve the gate on the hosting runtime’s own request path instead of by prompt (run approve --on <lexicon>). No runtime implements it: fountain’s needs the request-answer path from BinaryBourbon/fountain#1635, which has not shipped, and the flag refuses by naming that issue
-v, --verboseList every resolved build parameter, and show stack traces on errors
CodeMeaning
0Success (the run completed, or the command succeeded)
1Error (the run failed, the Op was not found, the runtime was unreachable)
3Gated: the run stopped at an unapproved gate and recorded the pending fact
--gated-exit <code>The gated outcome only, remapped to <code>. 0 and 1 keep their meanings above for every other outcome