Matrix Expansion¶
The matrix field fans a single task definition out into N parallel nodes — one per parameter combination. This eliminates copy-paste for tasks that differ only in their inputs.
Syntax¶
[tasks.build]
cmd = "GOOS={{.build.os}} GOARCH={{.build.arch}} go build -o bin/app-{{.build.os}}-{{.build.arch}} ."
matrix = {os = ["linux", "darwin", "windows"], arch = ["amd64", "arm64"]}
This produces 6 nodes:
build[os=linux,arch=amd64]
build[os=linux,arch=arm64]
build[os=darwin,arch=amd64]
build[os=darwin,arch=arm64]
build[os=windows,arch=amd64]
build[os=windows,arch=arm64]
Each node runs independently and in parallel (in --parallel or --work-stealing mode).
Single-Dimension Matrix¶
The most common case — one parameter, N values:
[tasks.backup-db]
cmd = "pg_dump {{.backup-db.db}} > /backups/{{.backup-db.db}}.sql"
matrix = {db = ["users", "orders", "inventory", "analytics"]}
Produces: backup-db[db=users], backup-db[db=orders], backup-db[db=inventory], backup-db[db=analytics].
Accessing Matrix Variables in Commands¶
Matrix variables are accessed in cmd using {{.taskid.paramname}}:
[tasks.test]
cmd = "go test ./... -run {{.test.suite}}"
matrix = {suite = ["unit", "integration", "e2e"]}
The variable name is test.suite — the task ID (test) followed by a dot and the parameter name (suite).
depends_on with Matrix Tasks¶
When a downstream task depends on a matrix task, it waits for all expanded nodes to complete:
[tasks.backup]
cmd = "dump {{.backup.db}}"
matrix = {db = ["postgres", "mysql", "redis"]}
[tasks.verify-all]
cmd = "echo 'All backups complete'"
depends_on = ["backup"] # waits for backup[db=postgres], [db=mysql], [db=redis]
If any matrix node fails (and ignore_failure = false), verify-all is marked cancelled.
register with Matrix Tasks¶
Each matrix node has its own register output. The variable is stored with a scoped key:
[tasks.backup]
cmd = "dump {{.backup.db}} && echo 'checksum_abc123'"
register = "checksum"
matrix = {db = ["postgres", "mysql"]}
Registers checksum for each node independently. The values are namespaced internally but downstream tasks can reference the last-written value via {{.checksum}} (last-completing node wins).
For deterministic access, use separate non-matrix tasks per database if the specific checksum per db matters.
ignore_failure on Matrix Tasks¶
ignore_failure = true applies to each expanded node independently. A failed node does not cancel sibling nodes:
[tasks.deploy]
cmd = "./deploy.sh {{.deploy.env}}"
matrix = {env = ["staging", "canary", "prod"]}
ignore_failure = true
All three deploy nodes run even if one fails.
Full Matrix Example¶
name = "cross-platform-release"
[tasks.build]
cmd = """
GOOS={{.build.os}} GOARCH=amd64 go build -o dist/app-{{.build.os}} .
echo "built {{.build.os}}"
"""
register = "build_status"
matrix = {os = ["linux", "darwin", "windows"]}
timeout = "5m"
[tasks.package]
cmd = "tar czf dist/app-{{.package.os}}.tar.gz dist/app-{{.package.os}}"
matrix = {os = ["linux", "darwin", "windows"]}
depends_on = ["build"]
timeout = "2m"
[tasks.publish]
cmd = "echo Publishing all packages..."
depends_on = ["package"]
Visualising Matrix Expansion¶
The HTML graph view shows each matrix node as a separate box with its parameter label.
Constraints¶
- Matrix parameters must be arrays of strings
- Parameter names must be valid variable names (letters, digits,
_,-,.) - Matrix expansion happens at DAG build time — the number of nodes is fixed before execution starts
- Circular dependencies between matrix nodes of the same task are not possible (they are siblings)