chore: prepare overnight automation 2026-03-03

Update loop scripts with rate limit handling, set plan for tonight's
run, and update prompt.md with task instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-03 18:24:17 +00:00
co-authored by Claude Opus 4.6
parent 99cb01277a
commit 3f9f650232
5 changed files with 132 additions and 13 deletions
+12
View File
@@ -104,6 +104,18 @@ Read, Glob, and Grep remain unrestricted.
| `ITERATION_DELAY` | `600` | Seconds between iterations when `ITERATION_COUNT` > 1. |
| `PROMPT_FILE` | `loop/prompt.md` | Prompt content for Claude. |
| `LOG_FILE` | `loop/loop.log` | Log output (gitignored). |
| `RATE_LIMIT_WAIT` | `3600` | Seconds to wait when rate limited (default 1 hour). |
| `MAX_RATE_LIMIT_RETRIES` | `5` | Max rate limit retries before scheduling launchd job. |
## Rate Limit Handling
The loop script automatically detects rate limits (429, quota exceeded, etc.) and handles them:
1. **Inline retry** — On first rate limit hit, sleeps for `RATE_LIMIT_WAIT` seconds (default 1 hour) and retries.
2. **Escalating retries** — Retries up to `MAX_RATE_LIMIT_RETRIES` times with the same wait.
3. **launchd fallback** — After max retries, creates a self-cleaning launchd plist at `~/Library/LaunchAgents/com.aiui.overnight-retry.plist` that restarts the loop at the estimated reset time. The plist auto-removes after running.
This means you can walk away knowing the automation will survive rate limits overnight.
## Scheduling (Optional)
+108 -9
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env sh
# Headless loop script for overnight Claude Code automation.
# Set CLAUDE_AUTONOMOUS=1 for Ralph Wiggum (Stop hook blocks until plan is complete).
# Rate-limit aware: detects limits, sleeps until reset, and retries automatically.
set -eu
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
@@ -9,18 +10,43 @@ LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
ITERATION_COUNT="${ITERATION_COUNT:-1}"
ITERATION_DELAY="${ITERATION_DELAY:-600}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
RATE_LIMIT_WAIT="${RATE_LIMIT_WAIT:-3600}" # Default: wait 1 hour on rate limit
MAX_RATE_LIMIT_RETRIES="${MAX_RATE_LIMIT_RETRIES:-5}" # Max retries before giving up
cd "$PROJECT_DIR"
echo "=== Overnight loop started $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ===" | tee -a "$LOG_FILE"
echo " PROMPT_FILE=$PROMPT_FILE" | tee -a "$LOG_FILE"
echo " CLAUDE_AUTONOMOUS=${CLAUDE_AUTONOMOUS:-0}" | tee -a "$LOG_FILE"
echo " ITERATION_COUNT=$ITERATION_COUNT" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
log() {
echo "$1" | tee -a "$LOG_FILE"
}
# Check if plan has remaining tasks
plan_has_tasks() {
grep -q '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null
}
# Detect rate limit from Claude output
check_rate_limit() {
# Check last 50 lines of log for rate limit indicators
tail -50 "$LOG_FILE" 2>/dev/null | grep -qi \
-e "rate.limit" \
-e "too.many.requests" \
-e "429" \
-e "quota.exceeded" \
-e "usage.limit" \
-e "limit.reached" 2>/dev/null
}
log "=== Overnight loop started $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ==="
log " PROMPT_FILE=$PROMPT_FILE"
log " CLAUDE_AUTONOMOUS=${CLAUDE_AUTONOMOUS:-0}"
log " ITERATION_COUNT=$ITERATION_COUNT"
log " RATE_LIMIT_WAIT=${RATE_LIMIT_WAIT}s"
log ""
i=1
rate_limit_retries=0
while [ "$i" -le "$ITERATION_COUNT" ]; do
echo "--- Iteration $i/$ITERATION_COUNT @ $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ---" | tee -a "$LOG_FILE"
log "--- Iteration $i/$ITERATION_COUNT @ $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ---"
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
@@ -29,15 +55,88 @@ while [ "$i" -le "$ITERATION_COUNT" ]; do
"$CLAUDE_BIN" -p --dangerously-skip-permissions --output-format=stream-json \
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE" || true
else
echo "Error: $PROMPT_FILE not found" | tee -a "$LOG_FILE"
log "Error: $PROMPT_FILE not found"
exit 1
fi
# Check for rate limit after Claude exits
if check_rate_limit; then
rate_limit_retries=$((rate_limit_retries + 1))
if [ "$rate_limit_retries" -ge "$MAX_RATE_LIMIT_RETRIES" ]; then
log "Rate limited $rate_limit_retries times — giving up."
log "Scheduling retry via launchd..."
# Schedule a retry using launchd for after rate limit resets
PLIST_LABEL="com.aiui.overnight-retry"
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
RETRY_TIME=$(date -v+${RATE_LIMIT_WAIT}S '+%H:%M' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M')
RETRY_HOUR=$(echo "$RETRY_TIME" | cut -d: -f1)
RETRY_MIN=$(echo "$RETRY_TIME" | cut -d: -f2)
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${PLIST_LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>cd ${PROJECT_DIR} && caffeinate -i ./loop/loop.sh >> ${LOG_FILE} 2>&1; launchctl unload ${PLIST_PATH}; rm -f ${PLIST_PATH}</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>${RETRY_HOUR}</integer>
<key>Minute</key>
<integer>${RETRY_MIN}</integer>
</dict>
<key>EnvironmentVariables</key>
<dict>
<key>CLAUDE_AUTONOMOUS</key>
<string>1</string>
<key>CLAUDE_PROJECT_DIR</key>
<string>${PROJECT_DIR}</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin</string>
</dict>
<key>StandardOutPath</key>
<string>${LOG_FILE}</string>
<key>StandardErrorPath</key>
<string>${LOG_FILE}</string>
</dict>
</plist>
PLIST
launchctl load "$PLIST_PATH" 2>/dev/null || true
log "Scheduled retry at ~${RETRY_TIME} via launchd ($PLIST_PATH)"
log "The plist auto-removes after running."
exit 0
fi
log "Rate limit detected (attempt $rate_limit_retries/$MAX_RATE_LIMIT_RETRIES)."
log "Sleeping ${RATE_LIMIT_WAIT}s until $(date -v+${RATE_LIMIT_WAIT}S '+%H:%M:%S' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M:%S')..."
sleep "$RATE_LIMIT_WAIT"
# Check if plan still has tasks before retrying
if ! plan_has_tasks; then
log "All plan tasks completed during rate limit wait. Done."
break
fi
log "Retrying after rate limit..."
continue # Retry same iteration
fi
# Reset rate limit counter on successful run
rate_limit_retries=0
i=$((i + 1))
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
echo "Waiting ${ITERATION_DELAY}s before next iteration..." | tee -a "$LOG_FILE"
log "Waiting ${ITERATION_DELAY}s before next iteration..."
sleep "$ITERATION_DELAY"
fi
done
echo "=== Loop complete $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ===" | tee -a "$LOG_FILE"
log "=== Loop complete $(date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || date) ==="
+2 -2
View File
@@ -1,4 +1,4 @@
# Evening Run — YYYY-MM-DD
# Evening Run — 2026-03-03
> **Format**: Use `- [ ]` for incomplete, `- [x]` for complete. Enable autonomous mode with `CLAUDE_AUTONOMOUS=1` when running.
@@ -10,7 +10,7 @@ Get the AIUI project to a green CI baseline: ESLint config, initial test coverag
- [ ] Committed all work; `git status` clean
- [ ] Pushed to remote (or at least backed up)
- [ ] Created branch for overnight: `git checkout -b overnight/YYYY-MM-DD`
- [ ] Created branch for overnight: `git checkout -b overnight/2026-03-03`
## Instructions for Claude
+9 -1
View File
@@ -1 +1,9 @@
Read `loop/plan.md` and work on the next incomplete task. Update the plan to mark completed items with `- [x]`. Add any relevant context to the Notes section for the next iteration. Do not stop until all tasks in the plan are complete.
Read loop/plan.md and work through the tasks in order. For each task:
1. Mark it as in-progress by updating plan.md
2. Complete the work
3. Mark it as done (`- [x]`) in plan.md
4. Add any relevant notes to the Notes section
5. Move to the next unchecked task
Follow CLAUDE.md conventions. Run `pnpm test`, `pnpm lint`, and `pnpm typecheck` after changes to verify nothing is broken. If a task is blocked, document why in the Notes section and move on.
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-cf23aef7'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.3epgdnb2r7k"
"revision": "0.jv7f3n1n5ic"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {