Skip to content

Instantly share code, notes, and snippets.

@chrismdp
Created March 1, 2026 14:17
Show Gist options
  • Select an option

  • Save chrismdp/64c2947fbd1e8b640aa3036412995209 to your computer and use it in GitHub Desktop.

Select an option

Save chrismdp/64c2947fbd1e8b640aa3036412995209 to your computer and use it in GitHub Desktop.
Claude Code Heartbeat: automated reminders via Telegram
#!/bin/bash
# Claude Code Heartbeat — check reminders and ping Telegram
#
# This script runs Claude Code every 15 minutes via cron to check
# a reminders file and send Telegram notifications when something
# needs attention. The server never sleeps, so reminders always fire.
#
# Setup:
# 1. Create a Telegram bot: message @BotFather, run /newbot, save the token
# 2. Get your chat ID: message @userinfobot on Telegram
# 3. Save credentials to ~/.secret_env:
# export TELEGRAM_BOT_TOKEN="your-bot-token"
# export TELEGRAM_CHAT_ID="your-chat-id"
# 4. Create ~/REMINDERS.md with your reminders (see format below)
# 5. Ask Claude Code to add this to your crontab:
# "Run heartbeat.sh every 15 minutes between 6am and 11pm via cron"
#
# Reminder format (~/REMINDERS.md):
# 2026-03-15 09:00 - Call the dentist
# every day 08:00 - Check email
# monday 09:00 - Weekly planning
# after 17:00 - Wind down, stop coding
#
# How it works:
# Cron runs this script → it spawns Claude Code with a prompt →
# Claude reads REMINDERS.md, checks the time, and calls send_telegram.sh
# for anything that matches. A lock file prevents overlapping runs.
set -euo pipefail
LOG=/tmp/heartbeat.log
LOCKFILE=/tmp/heartbeat.lock
# Ensure only one heartbeat runs at a time
exec 9>"$LOCKFILE"
if ! flock -n 9; then
# Check if the holder is stuck (>10 min)
HOLDER_PID=$(pgrep -f 'claude -p.*heartbeat' | head -1)
if [ -n "$HOLDER_PID" ]; then
ELAPSED=$(ps -p "$HOLDER_PID" -o etimes= 2>/dev/null | tr -d ' ')
if [ -n "$ELAPSED" ] && [ "$ELAPSED" -gt 600 ]; then
kill -9 "$HOLDER_PID" 2>/dev/null
sleep 1
flock -n 9 || exit 0
else
exit 0
fi
else
exit 0
fi
fi
# The prompt — tell Claude what to check and how to notify you
PROMPT='Check ~/REMINDERS.md against the current time.
For each reminder that matches now (within a 10-minute window),
send a Telegram notification using ~/bin/send_telegram.sh "message".
Skip any reminders outside the current time window.'
OUTPUT=$(claude -p "$PROMPT" --model sonnet 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "$(date '+%H:%M') exit=$EXIT_CODE $OUTPUT" >> "$LOG"
fi
date '+%H:%M' > /tmp/heartbeat-last-run
#!/bin/bash
# Send a message to Telegram
# Usage: send_telegram.sh "Your message here"
# Or: echo "message" | send_telegram.sh
source ~/.secret_env
CHAT_ID="${TELEGRAM_CHAT_ID}"
if [ -z "$1" ]; then
MESSAGE=$(cat)
else
MESSAGE="$1"
fi
if [ -z "$MESSAGE" ]; then
echo "Usage: send_telegram.sh \"message\" or pipe via stdin"
exit 1
fi
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
--data-urlencode "text=$MESSAGE" \
-d "chat_id=$CHAT_ID" \
-d "parse_mode=Markdown" > /dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment