Watchexec
A command runner that watches files and restarts or invokes commands when matching paths change.
Why consider Watchexec?
watchexec is a simple, standalone, language-agnostic CLI tool that watches a path and automatically executes a command whenever it detects file modifications. Written in Rust, it runs on Linux, macOS, and Windows, uses kernel-level filesystem event APIs for efficiency, coalesces rapid bursts of changes with debouncing, respects `.gitignore`/`.ignore` files, exposes changed paths via environment variables, and can restart long-running processes on each change — all without requiring any language runtime or cryptic `xargs` pipelines.
Learn Watchexec by building
Practical setup notes, real use cases, and copy-ready examples in one focused guide.
In this guide12 sections
watchexec — Run Commands Automatically on File Changes
watchexec is a simple, standalone tool that watches a path and runs a command whenever it detects file modifications. It is written in Rust, ships as a single binary with no required runtime, and works on Linux, macOS, and Windows.
Why watchexec?
Every developer repeats the same cycle: edit a file, switch to a terminal, run a build or test command, switch back. watchexec eliminates the manual switch by acting as an automated trigger between your editor and your toolchain.
Key properties verified from the source:
- No language runtime required — a single binary is all you need.
- Cross-platform — runs on Linux, Mac, Windows, and more using efficient kernel-level event APIs.
- Smart filtering — automatically respects
.gitignoreand.ignorefiles, so build artifacts and hidden files are silently excluded by default. - Debouncing — multiple filesystem events caused by editors that use swap/backup files during saving are coalesced into a single trigger.
- Process groups — uses process groups to keep hold of forking programs, so child processes are cleanly managed.
- Environment variables — exposes the exact paths that changed via
$WATCHEXEC_WRITTEN_PATH,$WATCHEXEC_CREATED_PATH,$WATCHEXEC_REMOVED_PATH,$WATCHEXEC_RENAMED_PATH,$WATCHEXEC_META_CHANGED_PATH, and$WATCHEXEC_OTHERWISE_CHANGED_PATH, enabling advanced scripting. --emit-events— event emission can be disabled with--emit-events=noneor switched to JSON via--emit-events=json-stdio.
Installation
watchexec is packaged for most major platforms. A few quick options:
# macOS / Linux via Homebrewbrew install watchexec# Arch Linuxpacman -S watchexec# Alpine Linuxapk add watchexec# Rust / Cargocargo install watchexec-cli# Debian / Ubuntu (using the .deb from GitHub Releases)dpkg -i watchexec-*.debPre-built binaries (.tar.xz, .deb, .rpm) are available on the GitHub Releases page.
Quick-Start Examples
The following examples are taken directly from the official README and CLI documentation.
Watch by file extension and run a build
# Watch all JavaScript, CSS and HTML files in the current directory# and all subdirectories for changes, running `npm run build` when a change is detected:$ watchexec -e js,css,html npm run buildRestart a long-running server on changes
# Call/restart `python server.py` when any Python file# in the current directory (and all subdirectories) changes:$ watchexec -r -e py -- python server.pyIgnore a directory
# Call `make test` when any file changes, except everything below `target`:$ watchexec -i "target/**" make testSend a custom stop signal
# Restart `my_server`, sending SIGKILL to stop it:$ watchexec -r --stop-signal SIGKILL my_serverWatch specific directories
# Watch lib and src directories for changes, rebuilding each time:$ watchexec -w lib -w src makeHow Event Filtering Works
watchexec applies filters in a strict order (documented in the CLI source):
- Internal prioritisation — signals (SIGINT/SIGTERM) are always processed first.
- File event kind — controlled by
--fs-events. - Explicit watch paths — files/dirs passed with
-w. - Ignores —
--ignoreflags and.gitignore/.ignorefiles. - Filters — including
--extsand glob patterns. - Filter programs — external programs used as custom filters.
Because .gitignore is loaded by default, your build artefacts directory will never accidentally trigger a rebuild.
Environment Variables Injected into Commands
watchexec sets $WATCHEXEC_COMMON_PATH to the longest common path of all changed paths. Each event-kind variable below should be prepended with that common path to obtain the full path. Multiple paths within one variable are separated by : on Unix and ; on Windows — matching the platform PATH convention.
| Variable | Event kind |
|---|---|
$WATCHEXEC_CREATED_PATH | files/folders were created |
$WATCHEXEC_REMOVED_PATH | files/folders were removed |
$WATCHEXEC_RENAMED_PATH | files/folders were renamed |
$WATCHEXEC_WRITTEN_PATH | files/folders were modified |
$WATCHEXEC_META_CHANGED_PATH | files/folders' metadata were modified |
$WATCHEXEC_OTHERWISE_CHANGED_PATH | every other kind of event |
Using watchexec as a Rust Library
watchexec also ships a first-party Rust library crate (watchexec) that powers the CLI. It is licensed under Apache 2.0. Here is the minimal setup taken from the library README:
use watchexec::{ command::{Command, Program, Shell}, Watchexec,};use watchexec_events::{Event, Priority};#[tokio::main]async fn main() -> miette::Result<()> { let wx = Watchexec::new(|mut action| { let (_, job) = action.create_job(std::sync::Arc::new(Command { program: Program::Shell { shell: Shell::new("bash"), command: "echo 'Hello world'".into(), args: Vec::new(), }, options: Default::default(), })); job.start(); action })?; let main = wx.main(); wx.send_event(Event::default(), Priority::Urgent).await.unwrap(); main.await.unwrap()?; Ok(())}API docs live at docs.rs/watchexec.
Tips and Troubleshooting
- Network shares / WSL: Native filesystem events may not fire. Add
--pollto fall back to polling. - Shell expansion: If your command contains globs, quote them so the shell does not expand them before handing them to watchexec. Compare
watchexec echo src/*.rsvswatchexec echo 'src/*.rs'. - Argfile support: Pass
@argfileas the first argument to load flags from a file (one argument per line), useful for complex configurations. - Desktop notifications: Use
--notify(where supported) to receive a desktop notification on command start and end. - Clear screen: Add
-cto clear the terminal before each execution, keeping output readable.
Related tools
More options with a similar category or technology profile.
diskus
Minimal, fast alternative to du -sh written in Rust using multi-threaded directory traversal.
peco
Simplistic interactive filtering tool for Unix pipelines, process lists, and file trees.
Dapr CLI
Command-line tool for managing Dapr distributed application runtime environments and sidecars.
Freeze
Generate beautiful image screenshots and SVGs of code snippets and terminal outputs.