Introduction
watchexec is the "watch these files, run this command" tool for everyone who's ever hacked together the same bash script with inotifywait or fswatch. Written in Rust, it works identically on macOS, Linux, and Windows, respects .gitignore by default, debounces rapid saves, and can restart processes on change (sending proper SIGTERM then SIGKILL).
With over 7,000 GitHub stars, watchexec powers many developer tools' "watch mode" under the hood (including cargo-watch built directly on top of it). If you've ever wanted a reliable file-watcher, this is the one.
What watchexec Does
watchexec monitors directory trees with native filesystem events (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows). When events arrive, it debounces them (coalesce bursts), applies include/exclude filters, and runs a shell command or replaces the running process.
Architecture Overview
[Filesystem]
|
[Native watcher: FSEvents / inotify / ReadDirectoryChangesW]
|
[Event Stream]
batched + debounced (100ms default)
|
[Filter Layer]
--exts / --filter-file / .gitignore / --ignore
|
[Exec Layer]
run command, or restart (-r)
signal: SIGTERM -> SIGKILL after grace period
clear screen (-c), emit bell (-N), notify (-n)Self-Hosting & Configuration
# Debounce changes over 500ms, ignore node_modules
watchexec --debounce 500 -i node_modules -- npm run build
# Multiple commands on change (shell)
watchexec -- "npm run lint && npm test"
# Restart a server, send SIGINT instead of SIGTERM
watchexec -r --signal SIGINT -- node server.js
# Watch only Go files, clear screen, send bell on run
watchexec -e go -c -N -- go test ./...
# Run once immediately then wait for changes
watchexec --on-busy-update=restart -w . --project-origin . -- ./dev.sh
# Using an environment file to pass changed file names
watchexec --emit-events-to=environment -- bash -c 'echo "$WATCHEXEC_WRITTEN_PATH changed"'# Common language-specific workflows
watchexec -e py -- pytest
watchexec -e go -- go test ./...
watchexec -e rs -- cargo test
watchexec -e ts,tsx -- bun run build
watchexec -e md -- mdbook build
# With pre-commit-style hooks
watchexec -e py -- "ruff check . && pytest -q"Key Features
- Cross-platform — same flags work on macOS, Linux, Windows
- .gitignore aware — skip ignored files by default
- Debouncing — coalesce bursts of saves into one run
- Process restart —
-rkills and restarts long-running servers cleanly - Extension filters —
-e go,mod,sumcommon shorthand - Clear / bell / notify — UX niceties built-in
- Event metadata — pass changed paths to the command via env vars
- Library + CLI — Rust library used by cargo-watch, lefthook, and others
Comparison with Similar Tools
| Feature | watchexec | entr | fswatch | inotifywait | nodemon |
|---|---|---|---|---|---|
| Language | Rust | C | C++ | C | Node |
| Cross-platform | Yes | Unix | Unix + partial Win | Linux only | Yes |
| Debouncing | Built-in | Manual | Manual | Manual | Built-in |
| .gitignore | Built-in | Manual | Manual | Manual | Built-in |
| Restart on change | Yes (-r) | Manual | Manual | Manual | Yes (focus) |
| Best For | Universal watcher | Unix hackers | Bash + fsevents | Linux-only workflows | Node-specific |
FAQ
Q: watchexec vs entr? A: watchexec is cross-platform, .gitignore-aware, debounced by default. entr is simpler (just reads file list from stdin) but Unix-only. watchexec is more robust for cross-platform dev teams.
Q: Does it stop on errors?
A: By default no — it keeps waiting for the next change. Combine with shell: watchexec -- "cmd || true" to suppress failure, or use --on-busy-update=signal for more control.
Q: Can I pass the changed file name to my command?
A: Yes — --emit-events-to=environment sets WATCHEXEC_WRITTEN_PATH, WATCHEXEC_CREATED_PATH, etc., in the command's env. Or --emit-events-to=stdio to pipe events as JSON.
Q: Does it work over network filesystems?
A: Partially. NFS and SMB may miss events (they depend on the filesystem driver). For critical workflows, watch a local mount or poll with --poll=200ms.
Sources
- GitHub: https://github.com/watchexec/watchexec
- License: Apache-2.0