Cron jobs fail quietly. An exit code 0 on a shell wrapper doesn't mean your ETL pipeline actually processed records—it often means a subprocess failed silently, timed out on an unhandled socket read, or was killed by the OOM reaper before writing output. Relying on local mail outputs or parsing syslog for critical cron jobs is an operational liability.
The Dead Man's Switch Pattern
Instead of paying $80/month for hosted heartbeat SaaS tools, you can enforce task visibility using a simple inverted watchdog pattern. The task signals its start, periodically pings a heartbeat during execution, and reports completion status. If the watchdog server misses a expected ping interval, it triggers alerts to Slack or Discord.
Implementing the Execution Wrapper
Below is a lightweight Python wrapper that encapsulates any CLI execution, captures runtime metrics, and handles graceful heartbeat pings before exiting with the target process status code.
import sys
import time
import subprocess
import urllib.request
import json
def pulse(endpoint: str, payload: dict):
try:
req = urllib.request.Request(
endpoint,
data=json.dumps(payload).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST'
)
urllib.request.urlopen(req, timeout=5)
except Exception as e:
sys.stderr.write(f"[CronPulse Warning] Failed to ping watchdog: {e}\n")
def run_task(watchdog_url: str, job_id: str, command: list):
pulse(f"{watchdog_url}/api/v1/ping/{job_id}/start", {"status": "running"})
start_time = time.time()
result = subprocess.run(command, capture_output=True, text=True)
duration = time.time() - start_time
payload = {
"exit_code": result.returncode,
"duration_seconds": round(duration, 2),
"stdout": result.stdout[-500:],
"stderr": result.stderr[-500:]
}
status_endpoint = "complete" if result.returncode == 0 else "fail"
pulse(f"{watchdog_url}/api/v1/ping/{job_id}/{status_endpoint}", payload)
sys.exit(result.returncode)
if __name__ == "__main__":
# Usage: python wrapper.py http://watchdog:8080 daily-backup -- tar -czf backup.tar.gz /data
run_task(sys.argv[1], sys.argv[2], sys.argv[4:])This wrapper uses pure standard library calls with zero external dependencies, making it suitable for lean container environments, minimal systemd setups, or legacy servers.
Systemd Integration and Deployment
# /etc/systemd/system/cronpulse-watchdog.service
[Unit]
Description=CronPulse Heartbeat Watchdog Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/cronpulse-server --config=/etc/cronpulse/config.yaml
Restart=always
RestartSec=5
User=cronpulse
[Install]
WantedBy=multi-user.target