Skip to content

The Task Runner

Your Makefile shells out to the wrong node. Your npm scripts can’t depend on a Go build step. just is nice but it’s a fifth tool to install. mise already manages your toolchain and your env — so it runs your chores too, with the right tools on PATH, real dependencies, parallelism, and incremental builds.

Terminal window
mise run build # run a task (recommended spelling)
mise r build # short alias
mise run # runs the task named "default"
mise tasks # list every task it can see

Define tasks inline in mise.toml. A one-liner is just run:

mise.toml
[tasks.build]
run = "go build -o bin/app ./..."
description = "Compile the server"
[tasks.lint]
run = ["ruff check .", "ruff format --check ."] # array = sequential steps

The full set of keys (use these verbatim — don’t invent others):

mise.toml
[tasks.deploy]
description = "Ship to staging"
run = "./scripts/deploy.sh"
run_windows = "pwsh ./scripts/deploy.ps1" # platform override
alias = "d" # SINGULAR (string or array)
depends = ["build", "test"] # run first, in parallel
depends_post = ["notify"] # run after this task succeeds
wait_for = ["migrate"] # soft: run after IF also scheduled
dir = "{{cwd}}" # default = config root
env = { TARGET = "staging" }
tools = { node = "22" } # per-task tool install
confirm = "Deploy to staging?" # prompt before running
KeyWhat it does
run / run_windowscommand string, or array = sequential steps
dependshard deps — run (in parallel) before this task
depends_postrun after this task finishes successfully
wait_forsoft ordering — only waits if the dep is also being run; never triggers it
dirworking dir (default: the config’s root; "{{cwd}}" = where you invoked from)
envextra env vars for this task
aliasshort name(s) — singular key, string or array
descriptionshown in mise tasks and completions
confirmy/n prompt string before running (great for destructive tasks)
toolstools to install/expose just for this task

For anything longer than a line, write a real script. Drop executables (chmod +x) into mise-tasks/ (or .mise-tasks/, mise/tasks/, .config/mise/tasks/). Subdirectories namespace with :mise-tasks/test/units becomes the task test:units.

  • Directorymise-tasks/
    • build
    • Directorytest/
      • units
      • e2e

Metadata lives in #MISE directive comments; arguments in #USAGE:

mise-tasks/build
#!/usr/bin/env bash
#MISE description="Build the release binary"
#MISE depends=["lint"]
#MISE sources=["src/**/*.go", "go.mod"]
#MISE outputs=["bin/app"]
#USAGE flag "-r --release" help="Optimized build"
#USAGE arg "<target>" default="./..."
set -euo pipefail
go build -o bin/app "$usage_target" # see "Arguments" below

A few language portability notes:

Terminal window
# [MISE] description="…" # formatter-safe form — survives shfmt/black reformatting
//MISE description="…" # for languages whose comments aren't '#' (JS, Go, Rust…)
//USAGE arg "<file>" # likewise for usage directives in non-bash scripts
mise-tasks/seed
#!/usr/bin/env python
#MISE description="Seed the dev database"
import os
print("seeding", os.environ["DATABASE_URL"])

depends builds a directed acyclic graph. mise runs independent branches in parallel and only serializes where a real dependency exists.

mise.toml
[tasks.build] # leaf — runs first
run = "go build ./..."
[tasks.lint]
run = "golangci-lint run"
depends = ["build"]
[tasks.test]
run = "go test ./..."
depends = ["build"]
[tasks.ci]
run = "echo all green"
depends = ["lint", "test"] # lint + test run in parallel; both wait on build
Task dependency DAG: mise run ci
Rendering diagram…

mise run ci runs build once, then lint and test together (highlighted), then ci. Inspect the tree without running anything:

Terminal window
mise tasks deps # show the dependency tree
mise tasks deps ci # just the subgraph for one task
Terminal window
mise run build # one task
mise run build ::: test # several tasks; ::: separates, each gets own args
mise run test -- --verbose -k auth # everything after -- is passed through
mise run "test:*" # glob over task names
mise run "{build,lint}" # brace expansion

Parallelism and control:

Terminal window
mise run ci --jobs 8 # up to 8 tasks at once (default 4; or MISE_JOBS)
mise run ci --jobs 1 # serialize — interleaves output deterministically
mise run ci --interleave # stream child output as it arrives
mise run deploy --skip-deps # run just this task, skip its depends
mise tasks # list (add --hidden to include hide=true tasks)

Declare a task’s inputs and outputs and mise skips it when the outputs are already newer than every source — same idea as make, but cross-language and lockstep with your toolchain.

mise.toml
[tasks.build]
run = "esbuild src/index.ts --bundle --outfile=dist/app.js"
sources = ["src/**/*.ts", "package.json"]
outputs = ["dist/app.js"]
[tasks.bundle-css]
run = "tailwindcss -i src/app.css -o dist/app.css"
sources = ["src/**/*.css", "tailwind.config.*", "!src/_scratch/**"] # ! excludes
outputs = { auto = true } # checksum every output dir automatically

The rule: skip when the oldest output is newer than the newest source. Use outputs = { auto = true } to track outputs by checksum instead of naming files — ideal when a build emits a whole directory.

Tasks take typed args via the usage spec. In TOML, set usage; in file tasks, use #USAGE directives. Parsed values land in $usage_<name> environment variables.

mise.toml
[tasks.greet]
usage = '''
arg "<name>" help="Who to greet"
flag "-l --loud" help="Shout it"
'''
run = '''
msg="hello $usage_name"
[ "$usage_loud" = "true" ] && msg=$(echo "$msg" | tr a-z A-Z)
echo "$msg"
'''
Terminal window
mise run greet world --loud # → HELLO WORLD

When mise.toml gets crowded, move tasks into dedicated files and pull them in with [task_config].includes:

mise.toml
[task_config]
includes = ["tasks.toml", "mytasks/"] # extra TOML files and/or task dirs

Working in a monorepo with tasks spread across packages? mise has first-class //path:task addressing for that — covered in Designing Your Workflow.

Makenpm scriptsjustmise
Polyglot (not JS-only)yesnoyesyes
Tools on PATH automaticallynopartialnoyes (your pinned versions)
Real dependency DAGyesnopartialyes (depends, parallel)
Incremental (skip-if-fresh)yes (mtime)nonoyes (sources/outputs, checksums)
Typed CLI argsnonopartialyes (usage spec)
Built-in parallelism-jnonoyes (--jobs)
Cross-platform (Windows)painfulyesyesyes (run_windows)

The pitch: mise tasks see the exact toolchain mise pinned for the project, so mise run build behaves the same on your laptop, a teammate’s, and CI.


Next: re-run tasks automatically on file changes and wire them into git hooks in Watch, Hooks & Automation, or design a team/monorepo task layout in Designing Your Workflow.