shoots pipeline

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

  1. What are you setting up? — work on a shoot, or train your develop profile.
  2. (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.
  3. The folders, and the artist name if exif is 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

Edit this page on GitHub