Created
March 31, 2026 13:42
-
-
Save kevinold/3d433c9549b17e6bb01dab763069ddd5 to your computer and use it in GitHub Desktop.
overnight-ce.sh — Queue GitHub issues, run each through /slfg overnight. Review PRs over morning coffee.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # overnight-ce.sh — Queue GitHub issues, run each through /slfg overnight. | |
| # Review PRs over morning coffee. ☕ | |
| # | |
| # Usage: | |
| # ./overnight-ce.sh # all issues labeled "agent-ready" | |
| # ./overnight-ce.sh --limit 5 # cap at 5 | |
| # ./overnight-ce.sh --label "priority" # different label | |
| # ./overnight-ce.sh --dry-run # preview only | |
| # | |
| # Prerequisites: | |
| # - gh CLI authenticated | |
| # - claude with compound-engineering plugin installed | |
| # - Inside your project git repo | |
| set -euo pipefail | |
| LABEL="${LABEL:-agent-ready}" | |
| LIMIT="${LIMIT:-0}" | |
| MAX_TURNS="${MAX_TURNS:-300}" | |
| DRY_RUN=false | |
| LOG_DIR="$HOME/.overnight-ce/logs" | |
| TIMESTAMP=$(date +%Y%m%d_%H%M%S) | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| --label) LABEL="$2"; shift 2 ;; | |
| --limit) LIMIT="$2"; shift 2 ;; | |
| --turns) MAX_TURNS="$2"; shift 2 ;; | |
| --dry-run) DRY_RUN=true; shift ;; | |
| -h|--help) sed -n '2,/^$/s/^# //p' "$0"; exit 0 ;; | |
| *) echo "Unknown option: $1"; exit 1 ;; | |
| esac | |
| done | |
| mkdir -p "$LOG_DIR" | |
| for cmd in gh claude jq git; do | |
| command -v "$cmd" >/dev/null 2>&1 || { echo "❌ $cmd not found"; exit 1; } | |
| done | |
| git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "❌ Not in a git repo"; exit 1; } | |
| STAGING_BRANCH="staging" | |
| # ── Fetch issues ──────────────────────────────────────────────────────────── | |
| echo "🔍 Fetching issues with label '$LABEL'..." | |
| LIMIT_FLAG=$( [[ "$LIMIT" -gt 0 ]] && echo "$LIMIT" || echo "100" ) | |
| ISSUES_JSON=$(gh issue list --label "$LABEL" --state open --json number,title,body --limit "$LIMIT_FLAG") | |
| ISSUE_COUNT=$(echo "$ISSUES_JSON" | jq length) | |
| if [[ "$ISSUE_COUNT" -eq 0 ]]; then | |
| echo "✅ No issues with label '$LABEL'. Nothing to do!" | |
| exit 0 | |
| fi | |
| echo "" | |
| echo "🌙 Overnight CE Run — $ISSUE_COUNT issues" | |
| echo "$ISSUES_JSON" | jq -r '.[] | " #\(.number): \(.title)"' | |
| echo "" | |
| if $DRY_RUN; then | |
| echo "🏜️ DRY RUN — nothing executed." | |
| exit 0 | |
| fi | |
| # ── Process each issue ────────────────────────────────────────────────────── | |
| SUCCEEDED=0 | |
| FAILED=0 | |
| for row in $(echo "$ISSUES_JSON" | jq -r '.[] | @base64'); do | |
| _jq() { echo "$row" | base64 --decode | jq -r "$1"; } | |
| ISSUE_NUM=$(_jq '.number') | |
| ISSUE_TITLE=$(_jq '.title') | |
| ISSUE_BODY=$(_jq '.body') | |
| LOG_FILE="$LOG_DIR/${TIMESTAMP}_issue-${ISSUE_NUM}.log" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "🚀 #${ISSUE_NUM}: ${ISSUE_TITLE}" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| git checkout "$STAGING_BRANCH" 2>/dev/null | |
| git pull --ff-only origin "$STAGING_BRANCH" 2>/dev/null || true | |
| PROMPT="Implement GitHub issue #${ISSUE_NUM}. | |
| Title: ${ISSUE_TITLE} | |
| ${ISSUE_BODY} | |
| Create your branch from this GitHub issue, then run /slfg to implement it end-to-end. The PR must reference \"Closes #${ISSUE_NUM}\". | |
| You are running autonomously in a ralph-loop — when you have clarifying questions, ask them and then answer them yourself using your best judgment based on the codebase, existing patterns, and docs. Document any key assumptions in the PR description. | |
| If you hit an unresolvable blocker, create a draft PR documenting it." | |
| echo " ⏳ Running /slfg... (started $(date +%H:%M:%S))" | |
| START_TIME=$(date +%s) | |
| if echo "$PROMPT" | claude -p --dangerously-skip-permissions --max-turns "$MAX_TURNS" > "$LOG_FILE" 2>&1; then | |
| DURATION=$(( ($(date +%s) - START_TIME) / 60 )) | |
| echo " ✅ Done in ~${DURATION}min" | |
| SUCCEEDED=$((SUCCEEDED + 1)) | |
| else | |
| DURATION=$(( ($(date +%s) - START_TIME) / 60 )) | |
| echo " ❌ Failed after ~${DURATION}min — tail -50 $LOG_FILE" | |
| FAILED=$((FAILED + 1)) | |
| fi | |
| echo "" | |
| done | |
| git checkout "$STAGING_BRANCH" 2>/dev/null || true | |
| # ── Summary ───────────────────────────────────────────────────────────────── | |
| echo "" | |
| echo "☀️ Done — ✅ $SUCCEEDED ❌ $FAILED" | |
| echo "" | |
| echo "☕ Review PRs:" | |
| echo " gh pr list --author @me --state open" | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment