DocsCommands
shoots pipeline
Run a YAML pipeline: shoots commands in order, sharing one set of variables.
shoots pipeline <config> [options] # run a pipeline file
shoots pipeline init [file] [options] # write one by answering questions
The full format is documented in Pipelines. This page is the
command surface. (shoots pipeline run <config> is the explicit spelling of the
first form; the bare shoots pipeline <config> is the same command.)
Arguments
| Argument | Required | Description |
|---|---|---|
<config> |
yes | Pipeline YAML file |
Options
| Option | Default | Description |
|---|---|---|
--var <name=value> |
— | Override a variable declared in the file. Repeatable. |
--from <id> |
— | Resume: skip every step before this one |
--dry-run |
off | Validate the file and print the command lines; run nothing |
--continue-on-error |
off | Keep going after a failing step, whatever the file says |
--json |
off | Machine-readable report on stdout (step output moves to stderr) |
--verbose |
off | Verbose logging on stderr |
Behaviour
Each step is this same build, re-invoked as a child process — so a step behaves exactly as it would if you had typed it, keeps its own progress view, and cannot take the pipeline's report down with it when it crashes.
The file is validated in full before the first step runs. Every command name,
every flag and every positional count is checked against the real command
definitions, and every problem is reported at once with its steps[i] location.
A typo in the last step fails in a second, not forty minutes in.
The first failing step stops the pipeline. Remaining steps are reported as
skipped, and the report ends with the --from <id> line that resumes from the
failure once you have fixed it.
Exit codes
| Code | Meaning |
|---|---|
0 |
Every step that ran succeeded |
1 |
At least one step failed |
2 |
The file could not be loaded, or does not resolve against this build's commands |
pipeline init
Writes a pipeline file by answering a handful of questions, for anyone who would rather not start from an empty YAML buffer.
shoots pipeline init [file] [options]
| Argument | Default | Description |
|---|---|---|
[file] |
shoots-pipeline.yaml |
Where to write |
| Option | Default | Description |
|---|---|---|
--template <name> |
— | Skip the questions: shoot or train, all defaults |
--var <name=value> |
— | Answer a variable up front, e.g. --var shoot=D:/Shoots/smith. Repeatable |
--name <name> |
preset's | Pipeline name written into the file |
--plain |
off | Ask line by line instead of the full-screen wizard |
--stdout |
off | Print the file instead of writing it |
--force |
off | Replace the file if it already exists |
--json |
off | Machine-readable report on stdout |
What it asks
- What are you setting up? — work on a shoot, or train your develop profile.
- (shoot only) The whole pass, or particular steps? — everything is
import → rename → exif → rate → cull → develop edit; picking opens the step list, for a folder that is already on disk. - The folders, and the artist name if
exifis in.
That is the whole wizard: two questions to set up training, five for a full shoot pass. It never asks anything a command already has a default for.
It writes scaffolding, not a finished config
The generated file carries only what no default could supply — the folder, the
artist name — plus the flags a command cannot run without (import --dest,
rename --pattern), the mark: true that makes rate and cull one pass
instead of two unrelated reports, and rename --recursive, without which a full
pass would rename nothing (the offload files into dated subfolders). Everything a step could take is a commented
hint under it:
steps:
- id: rating
run: rate
args: ${shoot}
with:
mark: true
# also: profile: street | generic | portrait | wildlife · write-xmp: true
So the file starts minimal and legible, teaches you the format as you read it, and grows by editing — which is what a pipeline is for. Nothing is frozen at today's defaults: when a command's default changes, a generated pipeline follows it.
Front-ends
On a terminal it opens the full-screen wizard: arrows move, space toggles a
step, enter accepts, esc goes back one answer, and the finished file is shown
before anything is written. --plain asks the same questions one line at a
time — press enter to take every default. Without a terminal it refuses rather
than guessing, so use --template for an unattended file.
Inside the shoots shell, /pipeline init runs the same wizard in-process
(the shell owns the terminal, so a child process has no stdin to ask with).
It accepts [file], --var and --name there; --template, --plain and
--stdout are spawned as an ordinary command, exactly as they are outside.
Whatever it writes is parsed and resolved against this build's real commands before it lands on disk, so a generated file always runs.
shoots pipeline init # the wizard
shoots pipeline init wedding.yaml --template shoot --var shoot=D:/Shoots/smith
shoots pipeline init --template train --stdout # print it, write nothing
Examples
Look before you leap:
shoots pipeline wedding.yaml --dry-run
Dry run — wedding-ingest: 6 step(s), nothing executed.
[1/6] offload
shoots import E:/DCIM/100CANON --concurrency 8 --dest D:/Shoots/2026/smith-wedding/raw
[2/6] name-frames
shoots rename D:/Shoots/2026/smith-wedding/raw --pattern {date}_{time}_{camera}_{seq:4}.{ext} --recursive
…
The same pipeline against a different shoot:
shoots pipeline wedding.yaml --var shoot=D:/Shoots/2026/jones-wedding
Resume after fixing what broke:
shoots pipeline wedding.yaml --from studio-tags
Headless, for cron or CI:
shoots pipeline nightly.yaml --json > run.json || echo "pipeline failed"
See also
- Pipelines — the YAML format, variables, and the flag mapping
- Scripting & automation — the shell-script alternative
schedule— runningdevelop refineunattended, nightly