A smarter watch utility that can trigger actions not just on file change, but on specific content changes.
watchfor is a command-line utility designed to repeatedly run a command or read a file until specific content is detected -state-based-. It serves as an intelligent and resilient command orchestrator that aims to prevent race conditions in CI/CD pipelines.
It repeatedly polls for a state (e.g., a health check returning "healthy", a log file containing "BUILD SUCCESSFUL") and then triggers a subsequent action and allows to handle transient failures and create robust automation.
It features:
- Command Polling: Repeatedly runs a command (e.g., a health check) and inspects its output.
- File Watching: Efficiently monitors a file (like a log) for new content, similar to
tail -f. - Resilience: Built-in support for maximum retries, exponential backoff, and a global timeout.
- Graceful Failure: Executes a fallback command (
--on-fail) if the condition is never met.
watchfor operates in one of two modes:
- Command Mode (
-cor--command): Executes a shell command at a regular interval and inspects its standard output. This is the primary mode for polling health checks or API endpoints. - File Mode (
-for--file): Reads the content of a specified file at a regular interval. This is useful for monitoring log files or build artifacts.
In both modes, if the pattern specified by -p is found, watchfor executes a success command. If the pattern is not found after all retries, it executes a failure command.
The command structure is:
watchfor [OPTIONS] -- [SUCCESS_COMMAND]| Flag | Description | Default |
|---|---|---|
-c, --command |
The command to execute and inspect. | |
-f, --file |
The path to the file to read and inspect. | |
-p, --pattern |
The exact string to search for in the output or file content. Required. | |
--regex |
Enable regex matching for the pattern. | false |
--ignore-case |
Enable case-insensitive matching for the pattern. | false |
--interval |
The initial interval between polling attempts (e.g., 5s, 1m). |
1s |
--max-retries |
Maximum polling attempts before giving up. 0 means retry forever. |
10 |
--backoff |
Exponential backoff factor (delay is multiplied by this factor each retry). A factor of 1 disables exponential backoff. |
1 |
--jitter |
The jitter factor to apply to the backoff delay (0 to 1). 0 disables jitter. |
0 |
--timeout |
Overall max wait time (e.g., 5m). Overrides --max-retries. |
0 (no timeout) |
--on-fail |
Command to execute if the pattern is not found after all attempts or on timeout. | |
-v, --verbose |
Enable verbose logging. | false |
When using the --regex flag, watchfor utilizes Go's standard regular expression syntax. You can find detailed documentation on the supported regex syntax here.
This single command will download and install watchfor to a sensible default location for your system.
User-level Installation (Recommended for most users):
Installs watchfor to $HOME/.local/bin (Linux/macOS) or a user-specific bin directory (Windows).
curl -sSfL https://raw.githubusercontent.com/gregory-chatelier/watchfor/main/install.sh | shSystem-wide Installation (Requires sudo):
Installs watchfor to /usr/local/bin (Linux/macOS).
sudo curl -sSfL https://raw.githubusercontent.com/gregory-chatelier/watchfor/main/install.sh | shCustom Installation Directory:
You can specify a custom installation directory using the INSTALL_DIR environment variable:
curl -sSfL https://raw.githubusercontent.com/gregory-chatelier/watchfor/main/install.sh | INSTALL_DIR=$HOME/bin shPolls a health endpoint every 5 seconds, with a back-off factor of 2, up to 10 times. If successful, it runs the test suite. If it fails, it sends an alert.
watchfor \
-c "curl -s https://api.myservice.com/health" \
-p '"status":"green"' \
--max-retries 10 \
--interval 5s \
--backoff 2 \
--on-fail "echo 'Service never became healthy' | mail -s 'Deploy failed' [email protected]; exit 1" \
-- ./run_tests.shMonitors a build log for the success message, timing out after 5 minutes.
watchfor \
--file "./build.log" \
--pattern "BUILD SUCCESSFUL" \
--timeout 5m \
--on-fail "echo 'Build failed or timed out'; exit 1" \
-- ./deploy.shwatchfor is designed to replace time-based waiting loops with resilient, state-based polling.
Before: The common pattern, a shell loop with sleep instructions
script:
#...
- echo "Health check..."
- |
for i in $(seq 1 10); do
if curl -f http://$HOST:$PORT/health; then
echo "Service is active!"
break
else
echo "Attempt $i/10: Service not yet active. Waiting 5 seconds..."
sleep 5
fi
done
if ! curl -f http://$HOST:$PORT/health; then
echo "Health check failed after multiple attempts."
exit 1
fiAfter: One single declarative and resilient command
script:
#...
- echo "Health check: waiting for service to become active..."
- |
watchfor \
-c "curl -sf http://$HOST:$PORT/health" \
-p "200" \
--max-retries 10 \
--interval 5s \
--on-fail "echo '❌ Health check failed after multiple attempts.'; \
exit 1" \
-- echo "✅ Service is active!"This project is licensed under the MIT License - see the LICENSE file for details.