Block until a condition is met. A simple, composable tool for orchestration workflows.
cd ~/src/hack/hold
zig build -Doptimize=ReleaseFast
# Binary at ./zig-out/bin/holdhold [OPTIONS] <command>
The command runs via /bin/sh -c, so full shell syntax works (pipes, redirects, etc.).
Exit code 0 = condition met. Non-zero = keep waiting.
| Flag | Description | Default |
|---|---|---|
-t, --timeout <duration> |
Maximum time to wait | 5m |
-i, --interval <duration> |
Time between checks | 2s |
-q, --quiet |
Suppress output | |
-v, --verbose |
Show each attempt | |
-h, --help |
Show help | |
-V, --version |
Show version |
5s 5 seconds
5m 5 minutes
1h 1 hour
1h30m 1 hour 30 minutes
500ms 500 milliseconds
# Wait for a file to exist
hold "test -f /tmp/done"
# Wait for a server to respond (max 30s, check every second)
hold -t 30s -i 1s "curl -sf localhost:8080/health"
# Wait for SSH to be available
hold -t 2m "ssh -o ConnectTimeout=2 sim1 true"
# Wait quietly (no output, just exit code)
hold -q "pgrep -q nginx" && echo "nginx is running"
# Wait with verbose output to see progress
hold -v -t 1m -i 5s "test -f /tmp/build-complete"| Code | Meaning |
|---|---|
| 0 | Condition was met |
| 1 | Timeout or error |
# File exists
hold "test -f /tmp/upload-complete"
# File is non-empty
hold "test -s /var/log/app.log"
# Directory exists
hold "test -d /mnt/volume"
# File contains specific content
hold "grep -q 'READY' /var/log/startup.log"
# File stops changing (same checksum twice)
hold "[ \"\$(md5sum /tmp/download)\" = \"\$(sleep 1; md5sum /tmp/download)\" ]"
# File has at least N lines
hold "[ \$(wc -l < /tmp/results.txt) -ge 100 ]"
# File modified in last 60 seconds
hold "find /tmp/heartbeat -mmin -1 | grep -q ."# Process is running
hold "pgrep -q nginx"
# Process is NOT running (wait for it to exit)
hold "! pgrep -q webpack"
# Specific PID is alive
hold "kill -0 12345 2>/dev/null"
# At least N instances running
hold "[ \$(pgrep -c python) -ge 3 ]"
# Process with specific arguments
hold "pgrep -f 'node server.js'"# TCP port is open
hold "nc -z localhost 8080"
# HTTP endpoint returns 200
hold "curl -sf http://localhost:8080/health"
# HTTP response contains expected content
hold "curl -s localhost:8080/status | grep -q 'ok'"
# HTTPS with self-signed cert
hold "curl -sfk https://localhost:8443/health"
# HTTP returns specific status code
hold "[ \$(curl -so /dev/null -w '%{http_code}' localhost:8080) = '200' ]"
# GraphQL/JSON endpoint check
hold "curl -s localhost:8080/graphql -d '{\"query\":\"{health}\"}' | jq -e '.data.health'"
# Wait for redirect to resolve
hold "curl -sI localhost | grep -q 'Location:'"# SSH is accepting connections
hold "ssh -o ConnectTimeout=2 -o BatchMode=yes host true"
# Remote file exists
hold "ssh host 'test -f /app/ready'"
# Remote process running
hold "ssh host 'pgrep -q myapp'"
# Remote command succeeds
hold "ssh host 'systemctl is-active myservice'"
# Multiple hosts ready
hold "ssh host1 true && ssh host2 true && ssh host3 true"# Container is running
hold "docker ps | grep -q mycontainer"
# Container health check passing
hold "[ \$(docker inspect --format='{{.State.Health.Status}}' mycontainer) = 'healthy' ]"
# Container exited successfully
hold "[ \$(docker inspect --format='{{.State.ExitCode}}' mycontainer) = '0' ]"
# Docker Compose service is up
hold "docker compose ps | grep -q 'myservice.*Up'"
# Specific container log message
hold "docker logs mycontainer 2>&1 | grep -q 'Server started'"
# Port exposed by container
hold "docker port mycontainer 8080"# PostgreSQL accepting connections
hold "pg_isready -h localhost -p 5432"
# MySQL/MariaDB ready
hold "mysqladmin ping -h localhost --silent"
# Redis responding
hold "redis-cli ping | grep -q PONG"
# MongoDB ready
hold "mongosh --eval 'db.runCommand({ping:1})' --quiet"
# Database has expected data
hold "psql -c 'SELECT 1 FROM users LIMIT 1' -t | grep -q 1"# Pod is running
hold "kubectl get pod mypod -o jsonpath='{.status.phase}' | grep -q Running"
# Deployment rolled out
hold "kubectl rollout status deployment/myapp --timeout=0s"
# All pods ready
hold "kubectl get pods -l app=myapp -o jsonpath='{.items[*].status.containerStatuses[*].ready}' | grep -v false"
# Service has endpoints
hold "kubectl get endpoints myservice -o jsonpath='{.subsets[*].addresses}' | grep -q ."
# Job completed
hold "kubectl get job myjob -o jsonpath='{.status.succeeded}' | grep -q 1"# Branch exists
hold "git rev-parse --verify origin/feature-branch"
# No uncommitted changes
hold "[ -z \"\$(git status --porcelain)\" ]"
# CI pipeline finished (GitHub)
hold "gh run list --workflow=ci.yml --limit=1 --json status -q '.[0].status' | grep -q completed"
# PR is mergeable
hold "gh pr view 123 --json mergeable -q '.mergeable' | grep -q MERGEABLE"# RabbitMQ queue has messages
hold "rabbitmqctl list_queues | grep myqueue | awk '{print \$2}' | grep -v '^0$'"
# Kafka topic exists
hold "kafka-topics.sh --list --bootstrap-server localhost:9092 | grep -q mytopic"
# AWS SQS queue not empty
hold "[ \$(aws sqs get-queue-attributes --queue-url \$QUEUE --attribute-names ApproximateNumberOfMessages --query 'Attributes.ApproximateNumberOfMessages' --output text) -gt 0 ]"# hold + box (agent has messages)
hold "box inbox myagent --json | jq -e 'length > 0'"
# hold + tix (task is ready)
hold "tix list --status todo --json | jq -e 'length > 0'"
# hold + sim (VM is initialized)
hold "ssh sim1 'test -f ~/.initFinished'"
# Compound conditions
hold "box inbox me --json | jq -e 'length > 0' && tix ready --json | jq -e 'length > 0'"# Build artifact exists
hold "test -f ./dist/bundle.js"
# Webpack dev server ready
hold "curl -sf localhost:3000"
# TypeScript compilation done (watching for .tsbuildinfo)
hold "test -f ./tsconfig.tsbuildinfo"
# Lock file released
hold "! test -f /tmp/build.lock"# CPU load below threshold
hold "[ \$(cat /proc/loadavg | cut -d' ' -f1 | cut -d'.' -f1) -lt 4 ]"
# Memory available (Linux)
hold "[ \$(awk '/MemAvailable/ {print \$2}' /proc/meminfo) -gt 1000000 ]"
# Disk space available
hold "[ \$(df /tmp | tail -1 | awk '{print \$4}') -gt 1000000 ]"
# Specific mount exists
hold "mountpoint -q /mnt/data"# Do something after condition met
hold "test -f /tmp/ready" && ./start-app.sh
# Handle timeout
hold -t 30s "curl -sf localhost" || echo "Server never came up"#!/bin/bash
set -e
echo "Starting database..."
docker compose up -d postgres
echo "Waiting for database..."
hold -t 60s "pg_isready -h localhost"
echo "Running migrations..."
./migrate.shif hold -q -t 5s "pgrep -q nginx"; then
echo "nginx is running"
else
echo "nginx not found, starting it..."
nginx
fi# See exactly what's happening
hold -v -t 30s -i 1s "curl -sf localhost:8080/health"
# attempt 1: checking condition...
# attempt 2: checking condition...
# ...
# condition met after 12.034s (12 attempts)hold is part of sprawl, a collection of composable tools for multi-agent orchestration.
| Tool | Description |
|---|---|
| tix | Lightweight issue tracker |
| box | Mailbox messaging for agent-to-agent communication |
| vent | Event logging for observability |
| mon | TUI monitor for humans |
MIT