Skip to content

Watch, Hooks & Automation

You have tasks. Now make them react: re-run on save, fire on cd, install on the first enter, and gate your commits. This page is the glue between “I defined a task” and “it runs itself at the right moment.”

mise watch — re-run a task on file change

Section titled “mise watch — re-run a task on file change”

mise watch wraps watchexec and re-runs a task whenever its files change.

Terminal window
mise use -g watchexec # required — mise shells out to it
mise watch build # re-runs the `build` task on change
mise w build # `w` is the alias

By default it watches the task’s sources — so the same globs that power incremental builds also tell watch what to look at:

mise.toml
[tasks.build]
run = "go build -o dist/app ./..."
sources = ["**/*.go"] # watch (and incremental-build) key
outputs = ["dist/app"]
Terminal window
mise watch build # watches **/*.go, rebuilds on save

mise watch owns exactly one flag of its own — --skip-deps. Everything else is forwarded to watchexec. The ones you’ll actually reach for:

FlagDoes
-r, --restartKill the running task and restart it on change (servers!)
-c, --clearClear the screen before each run
-e <ext>Watch by extension, e.g. -e rs or -e go,mod
-w <path>Watch specific path(s), overriding sources
-d <dur>Debounce, e.g. -d 500ms — collapse rapid saves
-s <sig>Signal to send on restart, e.g. -s SIGTERM
Terminal window
# Dev server: restart on any change under src/, only .rs files, clear each time
mise watch serve -w src -e rs --restart --clear
# Test loop with a debounce so a "save all" fires once
mise watch test -d 500ms

Hooks fire on lifecycle events instead of file changes. Define them in mise.toml:

mise.toml
[hooks]
enter = "echo 'entered $MISE_PROJECT_ROOT'" # first cd into the project tree
cd = "ls" # any cd within the project
leave = "echo bye" # cd out of the project tree
preinstall = "echo 'about to install tools'"
postinstall = "corepack enable" # after `mise install`
HookFires when
enterYou cd into the project directory (or a child of it)
cdYou cd anywhere within an already-entered project
leaveYou cd out of the project tree
preinstallBefore mise install resolves/installs tools
postinstallAfter mise install finishes
Hook lifecycle as you move in and out of a project
Rendering diagram…

So enter is a one-shot “you arrived” event; cd fires on every move while you’re inside; leave is your cleanup hook. Jumping straight from one mise project to another fires leave for the old one and enter for the new one.

Every hook script gets a few variables it can branch on:

VariableMeaning
MISE_PROJECT_ROOTRoot of the project the hook fired for
MISE_ORIGINAL_CWDThe directory you were in when the event fired
MISE_PREVIOUS_DIRWhere you came from (for cd/enter/leave)
MISE_INSTALLED_TOOLSJSON of just-installed tools (postinstall only)
mise.toml
[hooks]
# Auto-install tools the moment you enter a project that lacks them
enter = "mise install"

A normal hook runs in a subshell, so it can’t change your current shell’s state. For enter/cd/leave you can opt into running in the current shell — needed when you want to source something or export a var that sticks:

mise.toml
[hooks.enter]
shell = "bash"
script = "source ./scripts/dev-shell.sh" # runs in YOUR shell, not a subshell

Don’t inline a script when you already have a task — point the hook at it. Keeps the logic testable via mise run:

mise.toml
[hooks]
enter = { task = "setup" } # runs `mise run setup` on first entry
[tasks.setup]
run = "npm install && mise run db:migrate"

Values can also be an array (run several) or a table with run / run_windows / shell for cross-platform scripts:

mise.toml
[hooks]
enter = ["mise install", { task = "setup" }]
[hooks.cd]
run = "eza --git -l"
run_windows = "dir"

[[watch_files]] — react to specific files changing

Section titled “[[watch_files]] — react to specific files changing”

Distinct from mise watch (a foreground command): [[watch_files]] is an array-of-tables in config that fires a task/script automatically when matching files change during normal cd/prompt activity.

mise.toml
[[watch_files]]
patterns = ["package.json", "package-lock.json"]
task = "install" # or: run = "npm install"
[[watch_files]]
patterns = ["**/*.proto"]
run = "buf generate"

The changed paths are handed to your script as MISE_WATCH_FILES_MODIFIED:

mise.toml
[[watch_files]]
patterns = ["migrations/*.sql"]
run = "echo \"changed: $MISE_WATCH_FILES_MODIFIED\" && mise run db:migrate"

A tool can carry its own post-install step in the [tools] table — handy for enabling sidecar tooling right after the runtime lands:

mise.toml
[tools]
node = { version = "22", postinstall = "corepack enable" } # pnpm/yarn ready
python = { version = "3.13", postinstall = "pip install -U pip" }

The script receives MISE_TOOL_NAME and MISE_TOOL_VERSION so one snippet can adapt to the version just installed.

Generate a real .git/hooks/pre-commit that runs a mise task. Staged files arrive in $STAGED:

Terminal window
mise generate git-pre-commit --write --task pre-commit
mise.toml
[tasks.pre-commit]
run = "mise run lint && mise run test"
# $STAGED holds the staged file paths — lint only what's changing:
# run = "biome check $STAGED"

The generated hook simply calls mise run pre-commit, so the logic lives in your versioned mise.toml — not in an untracked .git/hooks/ file. We wire this into a team flow in Designing Your Workflow.

mise generate task-docs — document your tasks

Section titled “mise generate task-docs — document your tasks”

Keep a tasks reference in your README or docs in sync, generated from the task definitions themselves:

Terminal window
mise generate task-docs -o docs/tasks.md -s detailed
Terminal window
# Or inject between markers in an existing file (idempotent, CI-friendly):
mise generate task-docs --inject -o README.md

--inject rewrites only the block between <!-- mise-tasks --> markers, so it’s safe to run in CI to catch drift between your tasks and their docs.


Next: you’ve got the automation primitives — watch loops, lifecycle hooks, and git hooks. Designing Your Workflow assembles them into a team setup: shared mise.toml, bootstrap script, the trust model, and monorepo task fan-out.