Workflows

The second file format. A tasks.yml is a library of commands, each independently runnable; a workflow is one pipeline, run as a whole, with an order that is part of what it means.

Keys and defaults only: the cheat sheet.

  1. Which one you want
  2. Three levels
  3. What a workflow file can declare
  4. Two things that are deliberately missing

Which one you want

  tasks.yml workflows/*.yml
Is a library of commands one pipeline
Lives at .local-workflows/tasks.yml .local-workflows/workflows/*.yml
Ordering needs: between tasks stages:, then needs: between jobs
Run granularity any task, alone the whole file
version: optional required
name: ignored the row’s label

Reach for a workflow when the order is the point — a release that drafts notes, waits for a human, then publishes. Reach for tasks.yml when you have a pile of commands people run individually and some of them happen to depend on others.

Both run through the same task executor, so env:, vars:, dotenv:, cwd:, templating, trigger:, timeout:, retries:, if:, continueOnError: and output: mean the same thing in each.


Three levels

stages:          ordered, with a barrier between each
  jobs:          a DAG within the stage, ordered by needs:
    tasks:       a list, run in list order

Stages are sequential. No job in stage N+1 starts until every job in stage N has finished.

Jobs within a stage run together, unless one needs: another. Two jobs with no relationship between them run at the same time — because the file says they do not depend on each other, and a run that quietly serialised them would be making the declaration a lie.

Tasks are a list, in order. This is the one place the two formats genuinely differ: needs: on a task is a hard error, because a list already carries the order and two mechanisms disagreeing about it is worse than either alone.

Everything else about a task is identical to a tasks.yml task — a bare string is still the run: shorthand, run: still works, and the same closed set of keys applies. Both formats share one task parser; where a task sits changes only how it is addressed — by map key in a tasks.yml, by position in a job.


What a workflow file can declare

At the top level:

Key  
version: required, and must be 1
name: the row’s label in the tree
desc: shown beside it
params: values the run asks a human for before it starts
env:, vars:, dotenv: the same three blocks, at file level
plugins: file-local plugin aliases
ai: sugar for the plugins: entry named ai
stages: the pipeline

A stage may declare only name, desc, if, artifact, and jobs. A job may declare name, desc, needs, env, vars, dotenv, cwd, and tasks.

Both key sets are closed, and a misspelling is a hard error rather than a key nobody reads. That is the whole reason they are closed: a mistyped if: on a stage would otherwise be a phase that quietly always runs.


Two things that are deliberately missing

There is no job-level outputs: block. Data crosses job and stage boundaries through run variables — output: on the task that produces it, ${{ run.NAME.key }} where it is consumed — which are already run-scoped. A second mechanism would only be a narrower version of the one that exists.

version: cannot be omitted. A brand-new format with no legacy can afford to be strict, and that strictness is what buys painless evolution later. tasks.yml forgives an absent version only because every file written before versions existed has none.


Next: writing a new workflow.


Table of contents


Back to top

Local Workflows is a VS Code extension. Everything it does is declared in a YAML file you own.