Skip to content

DAG & parallelism

A pipeline’s steps form a directed acyclic graph (DAG). Each step waits for its dependencies; every step whose dependencies are finished starts immediately, so independent steps run in parallel.

In the step’s Flow section, Runs after chooses what the step waits for:

  • previous step (the default, dependsOn absent): the step waits for the step directly above it in the list. The first step in the list has no dependencies. A pipeline where no step sets Runs after is a simple sequence.
  • Specific steps (click their ids): the step waits for all of the selected steps. Selecting any step turns off previous step.
  • None ("dependsOn": []): the step starts as soon as the run starts. In the editor, click previous step on the first step, or deselect every chip; in JSON, write [].

The hint under the field reads “Steps with no dependencies start immediately; steps sharing a dependency run in parallel.”

Two steps that depend on the same step run in parallel. A step that depends on both of them waits for both: a join.

┌── tests (shell) ──┐
implement ──┤ ├── review ── approve ── ship
└── lint (shell) ───┘
[
{ "id": "implement", "type": "agent", "dependsOn": ["plan"], "…": "…" },
{ "id": "tests", "type": "shell", "command": "{{vars.testCommand | raw}}", "dependsOn": ["implement"] },
{ "id": "lint", "type": "shell", "command": "{{vars.lintCommand | raw}}", "dependsOn": ["implement"] },
{ "id": "review", "type": "agent", "dependsOn": ["tests", "lint"], "…": "…" }
]

Several steps with "dependsOn": [] all start at once. That’s useful for independent analyses (for example a security audit, a docs check and a changelog draft) that a final step then summarizes.

The graph at the top of the editor lays steps out left to right: each step sits one column to the right of its deepest dependency. Parallel steps stack vertically in the same column.

When the run is Running, the scheduler loops:

  1. For every Pending step whose dependencies are all finished (succeeded, failed, skipped or cancelled), decide what to do with it:
    • If any dependency failed or was cancelled without Continue on error, or was skipped because of an upstream failure, mark the step Skipped (upstream).
    • Otherwise start it.
  2. Wait for any running step to finish, then repeat.
  3. When nothing is running and nothing new can start, finish the run.

Consequences:

  • A step skipped by its Run only if condition counts as finished and does not block its dependents.
  • A failure skips everything downstream of it, but unrelated branches keep running to completion. For example if lint fails, tests still finishes, then review is skipped.
  • A step waiting for approval blocks only its own dependents. Other branches continue.
  • Steps are considered in list order, so when several become ready at once they start in list order (they still run concurrently).

There’s no limit on how many steps of one run execute at the same time: every ready step starts. Keep this in mind for steps that share the workspace:

  • Two agent steps editing files in the same worktree at the same time can conflict. Parallelize read-only work (reviews, analyses, tests, linters), and keep writers sequential.
  • Two shell steps that write to the same build output (dist/, node_modules/) can race.
  • Parallel git commands in one worktree can collide on .git/index.lock.

Two limits control how many runs execute at once:

Setting Default Scope
Settings → Max concurrent runs 3 (UI range 1–32) All pipelines. Extra runs wait in the queue as Queued.
Pipeline Max concurrent runs 0 (unlimited); Feature template 2, Bug fix template 3 Runs of this pipeline. 0 = only the global limit applies.

Queued runs start in creation order, except that a run whose pipeline is at its limit is passed over so runs of other pipelines can start.

Runs are isolated from each other by their workspace: Git worktree, Fresh clone and Empty folder give each run its own directory. Existing folder does not; set the pipeline’s limit to 1 if it edits files.

Dependencies must not form a cycle (“Step dependencies contain a cycle”). To send work back upstream, use a feedback loop (On failure, loop back to), which isn’t a dependency and isn’t part of the DAG.