Skip to content
/ hold Public

Block until a condition is met. Built in Zig.

Notifications You must be signed in to change notification settings

fielding/hold

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

hold

Block until a condition is met. A simple, composable tool for orchestration workflows.

Installation

cd ~/src/hack/hold
zig build -Doptimize=ReleaseFast
# Binary at ./zig-out/bin/hold

Usage

hold [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.

Options

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

Duration Format

5s        5 seconds
5m        5 minutes
1h        1 hour
1h30m     1 hour 30 minutes
500ms     500 milliseconds

Examples

# 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"

Exit Status

Code Meaning
0 Condition was met
1 Timeout or error

Practical Use Cases

File System

# 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 ."

Processes

# 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'"

Network & HTTP

# 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 & Remote

# 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"

Docker & Containers

# 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"

Databases

# 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"

Kubernetes

# 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"

Git & CI/CD

# 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"

Message Queues & Events

# 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 ]"

Custom Tool Integration

# 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 & Compilation

# 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"

System Resources

# 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"

Tips

Combine with && and ||

# 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"

Use in Scripts

#!/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.sh

Quiet Mode for Conditionals

if hold -q -t 5s "pgrep -q nginx"; then
    echo "nginx is running"
else
    echo "nginx not found, starting it..."
    nginx
fi

Verbose Mode for Debugging

# 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)

Part of sprawl

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

License

MIT

About

Block until a condition is met. Built in Zig.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages