#!/bin/bash # Main cron wrapper — runs the daily prompt against Claude Code set -uo pipefail # ─── Environment ────────────────────────────────────────────────────────────── # Cron doesn't source /etc/environment (PAM does that for interactive logins). # Source it explicitly so env vars (notifications, API tokens, etc.) are available. if [ -f /etc/environment ]; then set -a source /etc/environment set +a fi # Ensure locally-installed tools (like claude) are on PATH export PATH="$HOME/.local/bin:$PATH" # ─── Config ─────────────────────────────────────────────────────────────────── # IMPORTANT: Update PROJECT_DIR to the absolute path of your Daedalus installation PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" PROMPT_FILE="$PROJECT_DIR/prompts/daily.md" LOG_DIR="$PROJECT_DIR/logs" LOG_FILE="$LOG_DIR/daily-$(date +%Y%m%d-%H%M%S).log" MAX_TURNS="${MAX_TURNS:-200}" # Safety limit on agentic loops PID_FILE="$PROJECT_DIR/.claude/cron.pid" CHECKPOINT_FILE="$PROJECT_DIR/.claude/checkpoint.md" # Maximum age (in minutes) of the checkpoint before we consider the session stale. # If the checkpoint was updated within this window, the session is likely still working. STALE_THRESHOLD_MINUTES="${STALE_THRESHOLD_MINUTES:-30}" # ─── Previous Session Cleanup ──────────────────────────────────────────────── if [ -f "$PID_FILE" ]; then OLD_PID=$(cat "$PID_FILE") if kill -0 "$OLD_PID" 2>/dev/null; then # Process is still alive — check if it's actually working # by looking at how recently the checkpoint was updated SESSION_ACTIVE=false if [ -f "$CHECKPOINT_FILE" ]; then CHECKPOINT_AGE=$(( ( $(date +%s) - $(stat -c %Y "$CHECKPOINT_FILE") ) / 60 )) if [ "$CHECKPOINT_AGE" -lt "$STALE_THRESHOLD_MINUTES" ]; then SESSION_ACTIVE=true fi fi if [ "$SESSION_ACTIVE" = true ]; then echo "[$(date)] Previous session (PID $OLD_PID) is still active (checkpoint updated ${CHECKPOINT_AGE}m ago). Skipping this run." >> "$LOG_DIR/cron-skip.log" exit 0 else echo "[$(date)] Previous session (PID $OLD_PID) appears stale. Killing." >> "$LOG_DIR/cron-cleanup.log" kill "$OLD_PID" 2>/dev/null || true sleep 5 # Force kill if still alive if kill -0 "$OLD_PID" 2>/dev/null; then kill -9 "$OLD_PID" 2>/dev/null || true sleep 2 fi fi fi rm -f "$PID_FILE" fi # ─── Setup ──────────────────────────────────────────────────────────────────── mkdir -p "$LOG_DIR" cd "$PROJECT_DIR" echo "=============================================" >> "$LOG_FILE" echo "Daedalus Autonomous Session" >> "$LOG_FILE" echo "Started: $(date)" >> "$LOG_FILE" echo "Prompt: $PROMPT_FILE" >> "$LOG_FILE" echo "Max turns: $MAX_TURNS" >> "$LOG_FILE" echo "=============================================" >> "$LOG_FILE" # ─── Run ────────────────────────────────────────────────────────────────────── claude -p "$(cat "$PROMPT_FILE")" \ --dangerously-skip-permissions \ --max-turns "$MAX_TURNS" \ --output-format text \ >> "$LOG_FILE" 2>&1 & CLAUDE_PID=$! echo "$CLAUDE_PID" > "$PID_FILE" wait "$CLAUDE_PID" && EXIT_CODE=0 || EXIT_CODE=$? rm -f "$PID_FILE" echo "=============================================" >> "$LOG_FILE" echo "Finished: $(date)" >> "$LOG_FILE" echo "Exit code: $EXIT_CODE" >> "$LOG_FILE" echo "=============================================" >> "$LOG_FILE" exit $EXIT_CODE