Mise Tasks
Mise includes a built-in task runner that replaces task automation tools like Make, Just, and npm scripts. The task runner is integrated with mise so any tasks run with the correct language versions and tools required for a particular project.
A task runner lets you define repetitive commands once and run them with short, memorable aliases. Instead of typing long sequences to build, test, clean up, or deploy code, you run something like mise run test and the tool handles the rest. Task runners become essential as projects grow and teams collaborate, because everyone runs the same tasks the same way, reducing errors and setup friction.
Make, Just, and npm scripts are popular task runners, but none of them know which tool versions or environment variables a project needs. Mise solves this by combining version management and task automation in a single .mise.toml file. Tasks automatically run with the correct language versions, so a Python build step and a Node.js test run always use the right interpreters without extra configuration. Mise also runs independent tasks in parallel by default, honors dependencies, and can skip unchanged work. For teams, this means simpler onboarding and less time debugging mismatched environments, especially in projects mixing multiple languages.
Task automation is one step in setting up your Mac for development. See the full roadmap to set up a Mac for development.
Before you get started
You'll need a terminal application to install mise and run tasks. Apple includes the Mac terminal but I prefer Warp Terminal. Warp is an easy-to-use terminal application, with AI assistance to help you learn and remember terminal commands. Download Warp Terminal now; it's FREE and worth a try.
Why use Mise tasks
Experienced developers are familiar with task runners. Make is a Unix standard, Just is a popular lightweight alternative, and npm scripts handle JavaScript projects. Mise offers an advantage by solving a problem those tools ignore: other task runners do not know which tool versions or environment variables a project needs.
Run tasks with the right tool versions
When you run a task with mise, the task automatically inherits the tool versions and environment variables defined in your project's .mise.toml file. A test script that needs Node.js 24 and Python 3.14 gets exactly those versions without any extra setup. With Make or Just, you rely on whatever happens to be installed on the developer's machine, and mismatched versions cause hard-to-diagnose failures.
Simplify onboarding for new developers
New developers clone the repo, run mise install, and every tool version, environment variable, and task is ready. There is no separate Makefile to maintain alongside a .nvmrc, a .python-version, and a .env file. One .mise.toml file defines everything. Teams that have adopted Mise tasks say that errors from version mismatches disappear entirely.
Use TOML or plain shell scripts
Simple tasks stay in .mise.toml using standard TOML syntax. Complex tasks live as standalone shell scripts in a .mise/tasks/ directory. File-based tasks are ordinary bash scripts (or any language with a shebang), so they run with or without mise installed. This avoids the custom syntax that Make and Just require, and you do not need to learn a new language to write tasks.
Run tasks in parallel by default
Mise runs independent tasks in parallel with no configuration. Dependency-aware execution ensures tasks run in the correct order while still parallelizing whatever it can. In contrast, Make supports parallel builds but requires flags. Just does not support parallel execution at all.
Skip tasks when files have not changed
Mise can skip tasks when source files have not changed since the last run. This is useful for builds and linting where re-running on unchanged code wastes time. Make has this feature (it is central to how Make works), but Just and npm scripts do not.
Watch for changes
The mise watch command re-runs tasks automatically when files change. This replaces separate file-watching tools and integrates directly with your task definitions.
Here's how to use Mise tasks.
First steps to use Mise tasks
You need mise installed and activated. See Install Mise on Mac for setup. Check mise command not found on Mac if mise --version fails. You'll be editing a .mise.toml file in your project root, so you'll need a text editor. Basic familiarity with TOML syntax helps, but the examples here show exactly what to type.
Define tasks in .mise.toml
The simplest tasks use the TOML syntax. Create or edit .mise.toml in your project root and add a tasks section.
Here's a basic task:
[tasks.build]
run = "cargo build"
Run it with mise run build. The run property holds the command that executes. For tasks with multiple steps, use an array:
[tasks.deploy]
run = [
"npm run build",
"npm run test",
"npm run deploy-prod"
]
Each line runs in sequence. The task fails if any line fails.
Add descriptions to document what tasks do:
[tasks.test]
description = "Run tests with coverage"
run = "npm test -- --coverage"
The description appears when you list all tasks with mise tasks. Use this for any task that isn't self-explanatory.
Group related tasks using colons in the name. This organizes your task list and enables pattern matching:
[tasks.test:unit]
run = "npm run test:unit"
[tasks.test:integration]
run = "npm run test:integration"
[tasks.test:e2e]
run = "npm run test:e2e"
Now mise run test:unit runs just the unit tests. Pattern matching lets you run all test variants with mise run test:**.
Create file-based tasks
For complex tasks, especially those with lots of shell logic, write separate shell scripts. Create a .mise/tasks/ directory in your project and put executable files inside.
Here's a file-based task (.mise/tasks/build):
#!/bin/bash
# MISE description="Build the project with optimizations"
echo "Building with release profile..."
cargo build --release
Make the file executable:
$ chmod +x .mise/tasks/build
Now mise run build runs this script. The comment with # MISE description= sets the task description. File-based tasks support full shell scripting, making them better for complex logic than TOML.
Group file tasks using directories. A file at .mise/tasks/test/unit becomes task test:unit. Create nested groups:
.mise/tasks/
├── build
├── test/
│ ├── unit
│ ├── integration
│ └── e2e
└── db/
├── migrate
└── seed
This creates tasks: build, test:unit, test:integration, test:e2e, db:migrate, db:seed. The directory structure becomes part of the task name automatically.
You can mix TOML and file-based tasks. Simple tasks stay in .mise.toml for quick reference. Complex tasks live in scripts for readability.
Run tasks
The basic syntax is simple:
$ mise run taskname
$ mise run group:subtask
$ mise build # shorthand when no conflict
Use mise tasks to list all available tasks:
$ mise tasks
NAME DESCRIPTION
build Build the project
deploy Deploy to production
test:unit Run unit tests
test:integration Run integration tests
Tasks output directly to your terminal, so you see progress in real-time. If a task fails, the exit code propagates to your shell.
Run multiple tasks matching a pattern:
$ mise run test:**
This runs all tasks starting with "test:", such as test:unit, test:integration, and test:e2e.
Define task dependencies
Tasks often depend on other tasks. Use the depends property to specify prerequisites:
[tasks.build]
run = "cargo build"
[tasks.test]
depends = ["build"]
run = "cargo test"
[tasks.deploy]
depends = ["build", "test"]
run = "cargo deploy"
When you run mise run deploy, mise automatically runs build and test first, then deploy. If any dependency fails, the task stops without running. Dependencies run in parallel if they don't depend on each other, so even with many prerequisites, your workflow stays fast.
Use wait_for for optional sequencing. If the task exists and is running, the current task waits for it. Otherwise, it continues:
[tasks.start-services]
run = "docker-compose up"
[tasks.test]
wait_for = ["start-services"]
run = "npm test"
If someone runs mise run test directly, it proceeds without starting services. But if both are running together, the test waits for services.
Post-dependencies run after the main task completes, regardless of success or failure. Use them for cleanup or notifications:
[tasks.deploy]
run = "kubectl apply -f ."
depends_post = ["notify-team", "update-docs"]
The notify-team and update-docs tasks run after deployment, even if deployment fails.
Run tasks in parallel
Mise runs independent tasks in parallel by default. No configuration needed. If you have 4 independent test files that take 1 minute each, they run together in about 1 minute total instead of 4.
By default, mise uses 4 parallel jobs. Control this with a flag:
$ mise run test --jobs 8
Or set it as an environment variable:
$ MISE_JOBS=8 mise run test
Or configure it in .mise.toml:
[config]
jobs = 8
Mise respects dependencies, so even with high job counts, tasks run in the correct order. Parallel execution fails fast: if any task fails, remaining jobs are cancelled and the whole operation stops.
Set environment variables for tasks
Set environment variables for all tasks or for specific tasks. Global variables in the [env] section apply to every task:
[env]
NODE_ENV = "development"
DEBUG = "app:*"
[tasks.start]
run = "npm start"
Override global variables for individual tasks:
[env]
NODE_ENV = "development"
[tasks.test]
env.NODE_ENV = "test"
env.TEST_TIMEOUT = "5000"
run = "npm test"
When you run mise run test, NODE_ENV becomes "test" just for that task. The global NODE_ENV stays "development" for other tasks.
Separate variables from environment variables using [vars]. Variables interpolate into TOML with {{vars.name}} but don't become environment variables:
[vars]
build_args = "--release"
[tasks.build]
run = "cargo build {{vars.build_args}}"
Environment variables use $VARIABLE in shell scripts:
[env]
LOG_LEVEL = "debug"
[tasks.test]
run = "echo $LOG_LEVEL && npm test"
Group tasks with pattern matching
Colon-separated task names create hierarchy. Match entire groups or subsets with wildcards:
$ mise run build:** # All build:* tasks
$ mise run test:* # All test:* tasks
$ mise run **:cleanup # All tasks ending with :cleanup
This makes it easy to run related tasks without listing each one. Large projects benefit from grouping: database tasks under db:, frontend tasks under web:, backend tasks under api:.
File-based tasks group automatically by directory:
.mise/tasks/
├── db/
│ ├── migrate
│ ├── seed
│ └── backup
├── web/
│ ├── build
│ └── serve
Results in: db:migrate, db:seed, db:backup, web:build, web:serve. No configuration needed.
Mise task runner compared to other tools
Make is a longtime Unix standard, but Makefiles use a unique syntax that's hard to read. Mise tasks use TOML, which is clearer and more consistent. Mise also handles environment variables natively, supports parallel execution by default, and works with any language, not just C/C++.
npm scripts work well for Node.js projects but don't help multi-language teams or projects mixing languages. Mise tasks orchestrate builds, tests, and deploys across your entire tech stack in one file. If your project has Node.js, Python, and Rust code, one .mise.toml file with tasks can drive all of it.
Just is a lightweight task runner focused on simplicity, but it doesn't include version management. Mise combines version management and task running, so you don't need two separate tools.
The real strength of mise tasks is unification. One file defines tool versions, environment setup, and automation. New developers clone the repo, run mise install, and everything is ready. No separate Makefile, no npm scripts scattered in package.json, no shell scripts hidden in scripts/ directories.
Example of a multi-language project with mise tasks
Here's a real .mise.toml that shows how mise tasks orchestrate a mixed-language project. This example defines versions, environment variables, and tasks together in one file. For detailed configuration options, see Mise Configuration on Mac.
[tools]
node = "24"
python = "3.14"
rust = "1.70"
[env]
DATABASE_URL = "postgres://localhost/mydb"
[tasks.install]
run = [
"npm install",
"pip install -r requirements.txt",
"cargo build"
]
[tasks.lint]
run = [
"npm run lint",
"python -m pylint src/",
"cargo clippy"
]
[tasks.test:python]
run = "python -m pytest tests/python/"
[tasks.test:node]
run = "npm test"
[tasks.test:rust]
run = "cargo test"
[tasks.test:all]
depends = ["test:python", "test:node", "test:rust"]
run = "echo All tests passed"
[tasks.build]
depends = ["lint", "test:all"]
run = [
"npm run build",
"cargo build --release"
]
[tasks.deploy]
depends = ["build"]
run = [
"npm run deploy",
"python scripts/deploy.py",
"cargo deploy"
]
Run mise run build and it lints all three languages in parallel, tests all three in parallel, then builds. Running mise run deploy ensures build succeeds before deploying. One file orchestrates tools and workflows across Python, Node, and Rust.
Use monorepo support
Mise recently added support for monorepos where each package maintains its own .mise.toml file. The root .mise.toml sets experimental_monorepo_root = true:
experimental_monorepo_root = true
[config]
jobs = 8
Each app or package has its own .mise.toml. Tasks are named with their path prefix:
$ mise //...:build # Run build in all apps
$ mise //apps/web:test # Run test in apps/web only
$ mise //packages:lint # Run lint in all packages
This replaces tools such as Nx or Turborepo for teams that already use mise. Each project stays independent while keeping orchestration at the root level.
Troubleshoot mise tasks
If a task doesn't run, check that it's defined. Run mise tasks to list all available tasks. If a task name doesn't appear, verify the spelling and that the file is executable (for file-based tasks, use chmod +x).
If a task runs but a tool isn't found, make sure the tool is installed. Run mise install to install all tools specified in [tools]. If the tool is installed but the task can't find it, check that mise activation is in your shell configuration.
For parallel execution issues, use --jobs 1 to force serial execution and see which task fails. Parallel failures are harder to debug because multiple tasks run at once and output gets mixed.
If a task runs fine locally but fails in CI, ensure your CI environment has mise installed and properly configured. Many CI systems need Mise shims for mise to work.
Continue setting up your Mac
Don't miss the full visual roadmap and checklist that shows how to set up a Mac for software development, with all the essential tools and settings you might not yet know about.