breakglass-sync: per-owner soft budget (OWNER_BUDGET_SEC, default 2h)

Once an owner consumes more than OWNER_BUDGET_SEC seconds, the script
stops starting new repos for that owner and moves on to the next.
The in-flight repo finishes naturally — we never kill mid-push.
ONLY_REPO mode bypasses the budget (single-repo runs are intentional).

Surfaced via a new OWNERS_DEFERRED counter in the summary line + an
OWNER_BUDGET_EXCEEDED audit event per owner.

Originally added after signalapp was starved by openclaw repeatedly
consuming the whole window. With 12 owners and a 16h sync timeout,
2h/owner gives ample headroom while preventing any single owner from
monopolising the run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Piers Cockram 2026-05-28 12:57:01 +10:00
parent c0a66add46
commit 46c6e5a9c3

View File

@ -58,6 +58,12 @@ SYNC_RELEASES="${SYNC_RELEASES:-true}"
REVERSE_SYNC_REPOS="${REVERSE_SYNC_REPOS:-}"
GITHUB_PUSH_TOKEN="${GITHUB_PUSH_TOKEN:-}"
GITHUB_PUSH_OWNER="${GITHUB_PUSH_OWNER:-}"
# Per-owner soft budget. Once exceeded, the script stops starting new repos
# for that owner and moves on to the next. The in-flight repo (if any)
# finishes naturally — we never kill mid-push. Set to 0 to disable.
# Default 7200s (2h). Originally added 2026-05-28 after signalapp was
# starved by openclaw repeatedly consuming the whole window.
OWNER_BUDGET_SEC="${OWNER_BUDGET_SEC:-7200}"
mkdir -p "$MIRROR_ROOT" "$RELEASE_ROOT" "$LOG_DIR" "$AUDIT_DIR"
@ -68,6 +74,7 @@ ERRORS=0
SYNCED=0
SKIPPED=0
PROTECTED=0
OWNERS_DEFERRED=0
# ── Helpers ──────────────────────────────────────────────
@ -1092,9 +1099,24 @@ if [[ -z "$REVERSE_SYNC_ONLY" ]]; then
repos=$(gh_list_repos "$gh_owner") || { (( ERRORS++ )) || true; continue; }
fi
owner_start=$(date +%s)
while IFS= read -r repo; do
[[ -z "$repo" ]] && continue
# Per-owner budget: stop starting new repos once exceeded.
# The in-flight repo finishes naturally; we just don't begin another.
# ONLY_REPO mode bypasses the budget (single-repo runs are intentional).
if [[ -z "$ONLY_REPO" ]] && (( OWNER_BUDGET_SEC > 0 )); then
owner_elapsed=$(( $(date +%s) - owner_start ))
if (( owner_elapsed > OWNER_BUDGET_SEC )); then
warn "owner $gh_owner exceeded budget (${owner_elapsed}s > ${OWNER_BUDGET_SEC}s) — deferring remaining repos to next run"
audit "OWNER_BUDGET_EXCEEDED owner=$gh_owner elapsed=${owner_elapsed}s budget=${OWNER_BUDGET_SEC}s"
(( OWNERS_DEFERRED++ )) || true
break
fi
fi
if [[ -z "$ONLY_REPO" ]]; then
if ! matches_glob "$repo" "${include_glob:-*}"; then
(( SKIPPED++ )) || true; continue
@ -1126,7 +1148,7 @@ fi
DURATION=$(( $(date +%s) - $(date -d "$TIMESTAMP" +%s 2>/dev/null || date -u -d "${TIMESTAMP:0:8} ${TIMESTAMP:9:2}:${TIMESTAMP:11:2}:${TIMESTAMP:13:2}" +%s 2>/dev/null || echo 0) ))
DURATION_MIN=$(( DURATION / 60 ))
SUMMARY="Breakglass sync: ${SYNCED} synced, ${SKIPPED} skipped, ${PROTECTED} wipe-protected, ${ERRORS} errors (${DURATION_MIN}m)"
SUMMARY="Breakglass sync: ${SYNCED} synced, ${SKIPPED} skipped, ${PROTECTED} wipe-protected, ${ERRORS} errors, ${OWNERS_DEFERRED} owners deferred (${DURATION_MIN}m)"
log ""
log "═══ $SUMMARY ═══"
audit "SESSION_END $SUMMARY"