Skip to content

Watchman Cheat Sheet

Overview

Watchman is a file watching service created by Meta (Facebook) that monitors files and directories for changes, then triggers actions based on configurable rules. It uses OS-level file notification APIs (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows) for efficient, low-overhead watching.

Watchman is commonly used by tools like React Native, Buck, Jest, and other build systems that need to efficiently detect file changes across large codebases. It maintains a persistent daemon process that indexes file trees and responds to queries about file state, making subsequent watches nearly instant.

Installation

# macOS
brew install watchman

# Ubuntu/Debian
sudo apt install watchman

# From source
git clone https://github.com/facebook/watchman.git
cd watchman
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build
sudo cmake --install build

# Verify
watchman version
watchman watch-list

Core Commands

CommandDescription
watchman watch <dir>Start watching a directory
watchman watch-del <dir>Stop watching a directory
watchman watch-listList all watched directories
watchman trigger <dir> <name> <pattern> -- <cmd>Set up a trigger
watchman trigger-del <dir> <name>Remove a trigger
watchman trigger-list <dir>List triggers for a directory
watchman find <dir> <pattern>Find files matching pattern
watchman query <dir> <expr>Query files with expression
watchman since <dir> <clock>Files changed since clock value
watchman clock <dir>Get current clock value
watchman shutdown-serverStop the watchman daemon
watchman get-config <dir>Show root configuration

Watching Directories

# Watch a directory
watchman watch /path/to/project

# Watch current directory
watchman watch .

# Check watched roots
watchman watch-list

# Stop watching
watchman watch-del /path/to/project

# Watch a project (uses .watchmanconfig if present)
watchman watch-project /path/to/project

Triggers

Basic Triggers

# Run command when .js files change
watchman -- trigger /path/to/project build '*.js' -- npm run build

# Run linter on changed Python files
watchman -- trigger /path/to/project lint '*.py' -- python -m flake8

# Run tests when source or test files change
watchman -- trigger /path/to/project test '*.ts' '*.tsx' -- npm test

# List triggers
watchman trigger-list /path/to/project

# Remove a trigger
watchman trigger-del /path/to/project build

Advanced Trigger Configuration

# JSON trigger definition
watchman -j <<'EOF'
["trigger", "/path/to/project", {
  "name": "build-on-change",
  "expression": ["anyof",
    ["match", "*.ts"],
    ["match", "*.tsx"]
  ],
  "command": ["npm", "run", "build"],
  "append_files": false,
  "stdin": ["name", "exists", "new", "size", "mode"]
}]
EOF

# Trigger with directory filter
watchman -j <<'EOF'
["trigger", "/path/to/project", {
  "name": "test-src",
  "expression": ["allof",
    ["match", "*.py"],
    ["dirname", "src"]
  ],
  "command": ["pytest", "-x"]
}]
EOF

Queries

File Queries

# Find all JavaScript files
watchman -j <<'EOF'
["query", "/path/to/project", {
  "expression": ["match", "*.js"],
  "fields": ["name", "size", "mtime_ms"]
}]
EOF

# Find recently modified files
watchman -j <<'EOF'
["query", "/path/to/project", {
  "since": "c:1234:5678",
  "expression": ["anyof",
    ["match", "*.ts"],
    ["match", "*.tsx"]
  ],
  "fields": ["name", "new", "exists"]
}]
EOF

# Complex query
watchman -j <<'EOF'
["query", "/path/to/project", {
  "expression": ["allof",
    ["type", "f"],
    ["not", ["dirname", "node_modules"]],
    ["not", ["dirname", ".git"]],
    ["anyof",
      ["match", "*.ts"],
      ["match", "*.js"]
    ],
    ["since", "c:1234:5678"]
  ],
  "fields": ["name", "size", "mtime_ms", "exists"]
}]
EOF

Configuration

.watchmanconfig

{
  "settle": 20,
  "idle_reap_age_seconds": 86400,
  "ignore_dirs": [
    "node_modules",
    ".git",
    "dist",
    "build",
    "__pycache__",
    ".tox"
  ],
  "fsevents_latency": 0.05
}

Global Configuration

# Configuration file locations:
# /etc/watchman.json (global)
# /usr/local/etc/watchman.json (Homebrew)
# $HOME/.watchman.json (user)
{
  "root_restrict_files": [".git", ".watchmanconfig"],
  "enforce_root_files": true,
  "prefer_split_fsevents_watcher": true
}

Log and State Files

# Check log location
watchman get-sockname

# Common log locations
# macOS: /usr/local/var/run/watchman/<user>-state/
# Linux: /tmp/watchman-<user>-state/

# View logs
cat /usr/local/var/run/watchman/$USER-state/log

Advanced Usage

Subscriptions (Persistent Watches)

# Subscribe to file changes (used by IDEs and build tools)
watchman -j --persistent <<'EOF'
["subscribe", "/path/to/project", "my-subscription", {
  "expression": ["anyof",
    ["match", "*.ts"],
    ["match", "*.tsx"]
  ],
  "fields": ["name", "size", "exists", "type", "mtime_ms"]
}]
EOF

# Unsubscribe
watchman -j <<'EOF'
["unsubscribe", "/path/to/project", "my-subscription"]
EOF

State Management

# Get clock value (for incremental queries)
watchman clock /path/to/project

# Flush pending changes
watchman -j <<'EOF'
["flush-subscriptions", "/path/to/project", {
  "sync_timeout": 10000
}]
EOF

# Debug: check watch status
watchman -j <<'EOF'
["debug-contenthash", "/path/to/project", {
  "expression": ["name", "package.json"]
}]
EOF

Integration with Build Tools

# Used by Jest for fast file change detection
# jest.config.js
# { watchman: true }

# React Native uses watchman automatically
# Ensure it's installed before running:
npx react-native start

# Buck/Buck2 build system integration
# Watchman is auto-detected

Troubleshooting

IssueSolution
inotify limit reached (Linux)Increase: echo 65536 | sudo tee /proc/sys/fs/inotify/max_user_watches
Daemon not startingCheck socket permissions; run watchman shutdown-server and retry
Stale watchesRun watchman watch-del-all then re-add watches
High CPU usageAdd node_modules and build dirs to ignore_dirs
macOS permission deniedGrant Full Disk Access to Terminal in System Preferences
Changes not detectedCheck .watchmanconfig for overly broad ignore patterns
# Restart the daemon
watchman shutdown-server
watchman version  # auto-starts daemon

# Clear all watches
watchman watch-del-all

# Check daemon state
watchman get-sockname
watchman version

# Debug logging
watchman --logfile=/tmp/watchman-debug.log log-level 2

# Check inotify limits (Linux)
cat /proc/sys/fs/inotify/max_user_watches

# Permanently increase inotify limit
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p