Groups

A group runs several scripts under one name. Members are referenced by name and validated when the config loads, so a typo in a member name fails before anything spawns.

Info

Running several scripts walks through the three common shapes. This page is the field reference.

serial

Members run one at a time, in order, each with the terminal to itself.

ci: {
  serial: ['build', 'test', 'lint'],
  description: 'Build, then test, then lint',
}

The group stops at the first failure and exits with that member's code. Later members never start.

continueOnError: true runs every member regardless, and the group exits with the first failing code once they have all finished.

Because only one process runs at a time, a serial group behaves exactly like a single script: the child inherits the terminal, its output is not prefixed, and interactive tools work untouched.

$ rune run ci
compiled 14 packages in 3.2 s
41 passed, 41 total

dependsOn

dependsOn puts scripts in front of a single script rather than composing a new name.

test: {
  command: 'vitest run',
  dependsOn: ['build'],
}

Dependencies run serially, in the order listed, before the script itself. A failing dependency stops the run with its own code.

There is no pre and post naming convention. A script that should run first says so.

parallel

Members run at the same time, with their output piped and prefixed.

dev: {
  parallel: ['dev:api', 'dev:web'],
  description: 'Serve the API and the web app',
}
[dev:api] listening on http://localhost:4000
[dev:web] vite v6.0.1  ready in 412 ms
[dev:web] ➜ Local:   http://localhost:5173/

Prefix colours are stable per script for the life of the run. See Prefixed output for how interleaving, partial lines and colour are handled.

A parallel group with exactly one member is not piped and not prefixed. It runs with the terminal inherited, exactly as a single script would.

Failure

By default, one member failing terminates the rest. Each surviving member gets killSignal (SIGTERM by default), and SIGKILL after killTimeout if it is still alive. On Windows the member's whole process tree is terminated through its job object. A member that had already exited is not an error.

Output already produced by a terminated member is flushed before Rune exits, so a killed process never loses its last lines.

continueOnError: true lets the others finish. The group still fails.

successPolicy

ValueThe group succeeds when
all (default)Every member exits 0
firstThe first member to exit did so with 0
lastThe last member to exit did so with 0

first and last are evaluated in chronological exit order, not in the order the members are listed.

interactive members

A member that needs a real terminal keeps one:

dev: {
  parallel: ['dev:api', 'dev:shell'],
},
'dev:shell': {
  command: 'node --inspect repl.js',
  interactive: true,
}

An interactive member inherits standard input, output and error instead of being piped, so it is not prefixed and its output is not multiplexed. Use it for one member at most; two processes reading the same terminal fight over it.

Exit codes

SituationThe group exits with
Serial member failsThat member's code. Later members do not start
Parallel member failsThe first failing member's code, chronologically
successPolicy is satisfied0
Ctrl+CThe code the interrupted members produce, mapped the usual way

The full table is on Exit codes.

What a group cannot carry

A group entry accepts description, cwd, env, envFile, continueOnError and, for parallel, successPolicy. Everything else is refused when the config loads, each with its own message.

On a groupWhy it is refused
command, extends, appendArgsA script is exactly one kind
timeout, retries, retryDelay, killSignal, killTimeoutThey describe how one process is run, and a group is not a process
interactiveIt describes one process's relationship with the terminal, and only one member can own it
dependsOnA group runs the scripts it names and nothing before them
successPolicy on a serial groupMembers run one at a time, so first and last are already the member list

Put them on the members instead. To run something before a group, make it the first member of a serial group with the original one after it.

Pass-through arguments

A group has no command of its own, so there is nothing for pass-through arguments to join:

$ rune run ci --watch
`ci` runs other scripts, so it has no command for these arguments to join

arguments go to one command. Name the member that needs them:
  rune run <member> ...

Appending them to every member would run a flag against tools that never asked for it, and discarding them would lose what you typed without saying so.

Nesting

Groups nest. A parallel group may list a serial group as a member, which is how a sequence runs alongside a watcher. Nesting runs flat: the inner group's members run at the point the outer group reaches them, and an inner failure propagates outward under the same rules.

There are no dependency edges between the members of one group. Nesting is the mechanism for expressing structure.

Member names are checked at load time

Every name in serial, parallel and dependsOn is resolved when the config loads, not when the group runs. A name no script defines names the group, the member and the closest match:

script `ci` names `tset` as a member, which no script defines

did you mean `test`?

A group that reaches itself through any chain of members or prerequisites is a cycle error printing the path. Deferring either check to run time would let a typo in the third member surface only after the first two had already had effects.