From 713a4a68d2fb4ae054d07ef565c399c0760674f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:18:27 +0900 Subject: [PATCH 01/16] test(scheduler): reproduce self-amplifying org sweep cadence --- ...ions_queue_saturation_scheduler_cadence.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/test_actions_queue_saturation_scheduler_cadence.py diff --git a/tests/test_actions_queue_saturation_scheduler_cadence.py b/tests/test_actions_queue_saturation_scheduler_cadence.py new file mode 100644 index 0000000000..bcbe697523 --- /dev/null +++ b/tests/test_actions_queue_saturation_scheduler_cadence.py @@ -0,0 +1,23 @@ +"""Regression contract for the organization scheduler queue-saturation repair.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +WORKFLOW = ROOT / ".github" / "workflows" / "pr-review-merge-scheduler.yml" + + +def test_org_queue_sweep_is_hourly_not_quarter_hourly() -> None: + """The expensive org sweep must not self-amplify a saturated Actions queue.""" + workflow = WORKFLOW.read_text(encoding="utf-8") + assert '- cron: "0 * * * *"' in workflow or "- cron: '0 * * * *'" in workflow + assert '*/15 * * * *' not in workflow + + +def test_repository_scheduler_keeps_event_driven_wakes() -> None: + """Capacity repair must preserve event-driven admission rather than polling only.""" + workflow = WORKFLOW.read_text(encoding="utf-8") + assert "pull_request_target:" in workflow + assert "pull_request_review:" in workflow + assert "workflow_run:" in workflow + assert "repository_dispatch:" in workflow From 6f75ec5d3685ffc83cbc1612664ace0baf1a1b38 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:38:56 +0900 Subject: [PATCH 02/16] test(scheduler): bind rotation fallback to hourly sweep --- tests/test_actions_queue_saturation_scheduler_cadence.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_actions_queue_saturation_scheduler_cadence.py b/tests/test_actions_queue_saturation_scheduler_cadence.py index bcbe697523..17ce01f4b4 100644 --- a/tests/test_actions_queue_saturation_scheduler_cadence.py +++ b/tests/test_actions_queue_saturation_scheduler_cadence.py @@ -14,6 +14,13 @@ def test_org_queue_sweep_is_hourly_not_quarter_hourly() -> None: assert '*/15 * * * *' not in workflow +def test_org_queue_sweep_wall_clock_fallback_matches_hourly_cadence() -> None: + """Fallback rotation must advance once per hourly sweep, not four offsets at once.""" + workflow = WORKFLOW.read_text(encoding="utf-8") + assert workflow.count("$(date -u +%s) / 3600") == 2 + assert "$(date -u +%s) / 900" not in workflow + + def test_repository_scheduler_keeps_event_driven_wakes() -> None: """Capacity repair must preserve event-driven admission rather than polling only.""" workflow = WORKFLOW.read_text(encoding="utf-8") From 582fdc1f324b951a94ab02298a2db7db4c2d6fd0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:39:42 +0900 Subject: [PATCH 03/16] docs(queue): record hourly sweep root cause and safety boundary --- .../actions-queue-saturation-hourly-sweep.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docs/doctoring/actions-queue-saturation-hourly-sweep.md diff --git a/docs/doctoring/actions-queue-saturation-hourly-sweep.md b/docs/doctoring/actions-queue-saturation-hourly-sweep.md new file mode 100644 index 0000000000..a0d3122290 --- /dev/null +++ b/docs/doctoring/actions-queue-saturation-hourly-sweep.md @@ -0,0 +1,36 @@ +# Actions queue saturation: hourly organization sweep + +**Status:** active repair evidence +**Owning repository:** `ContextualWisdomLab/.github` +**Canonical repair PR:** `#1630` +**Protected baseline:** `main@4ae90e18b03a3a455e13e501628010cabc5c37a8` + +## Root cause + +The central PR review/merge scheduler has two periodic entry points in addition to event-driven wakes. The repository-local queue scan runs every 30 minutes, while the expensive `org-queue-sweep` has been admitted every 15 minutes. Under the observed organization-wide hosted-runner saturation, the full organization walk can remain queued or run long enough that quarter-hourly admission adds more pending work before prior evidence drains. That is a control-plane pressure amplifier: required current-head evidence for leaf repositories queues behind recurring control-plane work that exists to unblock those same repositories. + +The repair is deliberately bounded. Keep the 30-minute repository scan and all event-driven `pull_request_target`, `pull_request_review`, `workflow_run`, and `repository_dispatch` wakes. Change only the organization sweep heartbeat to hourly (`0 * * * *`). The wall-clock fallback used by the persisted sweep rotation counter must advance on the same hourly cadence (`epoch_seconds / 3600`) rather than the old 15-minute cadence (`epoch_seconds / 900`), otherwise a fallback run would skip four repository offsets for each real scheduled sweep. + +## TDD and executable contract + +`tests/test_actions_queue_saturation_scheduler_cadence.py` is the RED-first contract. It requires the live workflow to contain the hourly cron, rejects the quarter-hourly cron, preserves event-driven wakes, and binds both wall-clock fallback expressions to hourly rotation. The older assertions in `tests/test_required_workflow_queue_contract.py` must be updated with the production workflow rather than retained as a stale policy test. + +The production change must also update `docs/org-required-workflow-rollout.md` so operator guidance states that the heartbeat can be up to one hour old. Historical doctoring that describes the old quarter-hour schedule remains historical evidence and must not be rewritten as though it never existed. + +## Safety boundary + +This repair does not mark queued checks successful, cancel the sole current-head evidence, weaken required workflows, relax approval requirements, or synthesize review state. Queue hygiene remains fail-closed. Cross-repository mutation credentials, exact-head validation, stale-head cancellation rules, unavailable-repository thresholds, scheduler concurrency groups, and merge guards remain unchanged. + +No organization-owned identifier introduced by this repair uses an ambiguous single-word domain name. GitHub event fields and cron syntax are externally mandated contract terms and remain unchanged except for the cadence value. + +## Verification + +After the production commit lands on the canonical branch: + +1. run the focused cadence and required-workflow queue contract tests; +2. verify the scheduler workflow contains exactly the intended 30-minute repository scan and hourly organization sweep; +3. confirm event-driven wakes remain present; +4. inspect fresh exact-head required checks and review evidence; +5. observe queue depth after the change rather than treating the configuration diff itself as proof that saturation has cleared. + +Merge remains subject to ordinary protected-branch requirements and exact-current-head evidence. From 967eddd63b5a4980c33d0b141437c8b09fbd6f1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:39:52 +0900 Subject: [PATCH 04/16] chore(ci): materialize PR 1630 scheduler repair --- .../repair-pr1630-scheduler-cadence.yml | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 .github/workflows/repair-pr1630-scheduler-cadence.yml diff --git a/.github/workflows/repair-pr1630-scheduler-cadence.yml b/.github/workflows/repair-pr1630-scheduler-cadence.yml new file mode 100644 index 0000000000..11ee9bb1b7 --- /dev/null +++ b/.github/workflows/repair-pr1630-scheduler-cadence.yml @@ -0,0 +1,124 @@ +name: Repair PR 1630 scheduler cadence + +on: + push: + branches: + - fix/actions-queue-saturation-scheduler-cadence-20260902 + paths: + - .github/workflows/repair-pr1630-scheduler-cadence.yml + +permissions: {} + +jobs: + repair: + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - name: Harden runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + disable-file-monitoring: true + + - name: Checkout exact writer head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 2 + + - name: Materialize and verify hourly sweep repair + shell: bash + env: + WRITER_BRANCH: fix/actions-queue-saturation-scheduler-cadence-20260902 + EXPECTED_HEAD: ${{ github.sha }} + run: | + set -euo pipefail + remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" + test "$remote_head" = "$EXPECTED_HEAD" + test "$(git rev-parse HEAD)" = "$EXPECTED_HEAD" + + python3 - <<'PY' + from pathlib import Path + + workflow_path = Path('.github/workflows/pr-review-merge-scheduler.yml') + workflow = workflow_path.read_text(encoding='utf-8') + assert workflow.count('- cron: "*/15 * * * *"') == 1 + assert workflow.count("github.event.schedule == '*/15 * * * *'") == 1 + assert workflow.count('$(date -u +%s) / 900') == 2 + workflow = workflow.replace('Every-15-minutes org-wide sweep cadence', 'Hourly org-wide sweep cadence', 1) + workflow = workflow.replace('Runs every 15 minutes so an approval or', 'Runs hourly so an approval or', 1) + workflow = workflow.replace('within ~15 minutes instead of sitting idle for up to an hour.', 'within ~1 hour instead of sitting idle indefinitely.', 1) + workflow = workflow.replace('- cron: "*/15 * * * *"', '- cron: "0 * * * *"', 1) + workflow = workflow.replace("github.event.schedule == '*/15 * * * *'", "github.event.schedule == '0 * * * *'", 1) + workflow = workflow.replace('one running and one latest pending */15 sweep', 'one running and one latest pending hourly sweep', 1) + workflow = workflow.replace('900s', '3600s') + workflow = workflow.replace('$(date -u +%s) / 900', '$(date -u +%s) / 3600') + assert '*/15 * * * *' not in workflow + assert workflow.count('$(date -u +%s) / 3600') == 2 + workflow_path.write_text(workflow, encoding='utf-8') + + contract_path = Path('tests/test_required_workflow_queue_contract.py') + contract = contract_path.read_text(encoding='utf-8') + assert contract.count('- cron: "*/15 * * * *"') == 1 + assert contract.count("github.event.schedule == '*/15 * * * *'") == 1 + contract = contract.replace('every 15 minutes so an approval', 'hourly so an approval', 1) + contract = contract.replace('- cron: "*/15 * * * *"', '- cron: "0 * * * *"', 1) + contract = contract.replace("github.event.schedule == '*/15 * * * *'", "github.event.schedule == '0 * * * *'", 1) + assert '*/15 * * * *' not in contract + contract_path.write_text(contract, encoding='utf-8') + + docs_path = Path('docs/org-required-workflow-rollout.md') + docs = docs_path.read_text(encoding='utf-8') + assert '*/15 * * * *' in docs + docs = docs.replace('*/15 * * * *', '0 * * * *') + docs = docs.replace('every 15 minutes', 'hourly') + docs = docs.replace('Every 15 minutes', 'Every hour') + docs = docs.replace('at most 15 minutes old', 'at most one hour old') + docs = docs.replace('within 15 minutes', 'within one hour') + docs = docs.replace('15-minute org sweep', 'hourly org sweep') + docs = docs.replace('15-minute sweep', 'hourly sweep') + docs = docs.replace('15-minute cadence', 'hourly cadence') + assert '*/15 * * * *' not in docs + docs_path.write_text(docs, encoding='utf-8') + PY + + python3 - <<'PY' + import runpy + scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') + for name, value in sorted(scope.items()): + if name.startswith('test_') and callable(value): + value() + print(f'PASS {name}') + PY + + python3 - <<'PY' + from pathlib import Path + workflow = Path('.github/workflows/pr-review-merge-scheduler.yml').read_text(encoding='utf-8') + contract = Path('tests/test_required_workflow_queue_contract.py').read_text(encoding='utf-8') + assert '- cron: "*/30 * * * *"' in workflow + assert '- cron: "0 * * * *"' in workflow + assert "github.event.schedule == '0 * * * *'" in workflow + assert "pull_request_target:" in workflow + assert "pull_request_review:" in workflow + assert "workflow_run:" in workflow + assert "repository_dispatch:" in workflow + assert 'org-queue-sweep:' in workflow + assert '- cron: "0 * * * *"' in contract + assert "github.event.schedule == '0 * * * *'" in contract + assert workflow.count('$(date -u +%s) / 3600') == 2 + print('PASS required scheduler queue cadence contract') + PY + + git diff --check + rm .github/workflows/repair-pr1630-scheduler-cadence.yml + git add .github/workflows/pr-review-merge-scheduler.yml tests/test_actions_queue_saturation_scheduler_cadence.py tests/test_required_workflow_queue_contract.py docs/org-required-workflow-rollout.md .github/workflows/repair-pr1630-scheduler-cadence.yml + git diff --cached --check + + remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" + test "$remote_head" = "$EXPECTED_HEAD" + + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git commit -m "fix(scheduler): reduce org sweep pressure under saturation" + git push origin "HEAD:${WRITER_BRANCH}" From 819e854dafd62015b88720d00c03117387ce9951 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:40:40 +0900 Subject: [PATCH 05/16] chore(queue): materialize bounded scheduler cadence repair --- .../repair_actions_queue_cadence.yml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 .github/workflows/repair_actions_queue_cadence.yml diff --git a/.github/workflows/repair_actions_queue_cadence.yml b/.github/workflows/repair_actions_queue_cadence.yml new file mode 100644 index 0000000000..6a5c1f31b4 --- /dev/null +++ b/.github/workflows/repair_actions_queue_cadence.yml @@ -0,0 +1,119 @@ +name: Repair Actions queue scheduler cadence + +on: + push: + branches: + - fix/actions-queue-saturation-scheduler-cadence-20260902 + +permissions: + contents: write + +concurrency: + group: actions-queue-cadence-repair-${{ github.ref }} + cancel-in-progress: true + +jobs: + repair_scheduler_cadence: + if: ${{ github.actor != 'github-actions[bot]' }} + runs-on: ubuntu-24.04 + steps: + - name: Check out exact canonical branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/actions-queue-saturation-scheduler-cadence-20260902 + fetch-depth: 0 + + - name: Apply bounded hourly sweep repair + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + + scheduler_path = Path('.github/workflows/pr-review-merge-scheduler.yml') + scheduler_source = scheduler_path.read_text(encoding='utf-8') + old_cron = '*/15 * * * *' + hourly_cron = '0 * * * *' + assert scheduler_source.count(old_cron) >= 3 + assert scheduler_source.count('$(date -u +%s) / 900') == 2 + scheduler_source = scheduler_source.replace(old_cron, hourly_cron) + scheduler_source = scheduler_source.replace( + 'Every-15-minutes org-wide sweep cadence', + 'Hourly org-wide sweep cadence', + ) + scheduler_source = scheduler_source.replace( + 'Runs every 15 minutes so an approval or', + 'Runs hourly so an approval or', + ) + scheduler_source = scheduler_source.replace( + "within ~15 minutes instead of sitting idle for up to an hour.", + 'within about an hour without adding quarter-hourly runner pressure.', + ) + scheduler_source = scheduler_source.replace( + 'one latest pending 0 * * * * sweep', + 'one latest pending hourly sweep', + ) + scheduler_source = scheduler_source.replace( + '$(date -u +%s) / 900', + '$(date -u +%s) / 3600', + ) + assert old_cron not in scheduler_source + assert scheduler_source.count(hourly_cron) >= 3 + assert scheduler_source.count('$(date -u +%s) / 3600') == 2 + scheduler_path.write_text(scheduler_source, encoding='utf-8') + + queue_contract_path = Path('tests/test_required_workflow_queue_contract.py') + queue_contract_source = queue_contract_path.read_text(encoding='utf-8') + assert queue_contract_source.count(old_cron) >= 2 + queue_contract_source = queue_contract_source.replace(old_cron, hourly_cron) + queue_contract_source = queue_contract_source.replace( + 'every 15 minutes so an approval that lands after a PR\'s last event is', + 'hourly so an approval that lands after a PR\'s last event is', + ) + assert old_cron not in queue_contract_source + queue_contract_path.write_text(queue_contract_source, encoding='utf-8') + + rollout_path = Path('docs/org-required-workflow-rollout.md') + rollout_source = rollout_path.read_text(encoding='utf-8') + assert old_cron in rollout_source + rollout_source = rollout_source.replace(old_cron, hourly_cron) + rollout_source = rollout_source.replace('runs every 15 minutes', 'runs hourly') + rollout_source = rollout_source.replace('at most 15 minutes old', 'at most one hour old') + assert old_cron not in rollout_source + rollout_path.write_text(rollout_source, encoding='utf-8') + PY + + - name: Verify focused executable contracts + shell: bash + run: | + set -euo pipefail + python -m pytest -q tests/test_actions_queue_saturation_scheduler_cadence.py + python - <<'PY' + from pathlib import Path + workflow_source = Path('.github/workflows/pr-review-merge-scheduler.yml').read_text(encoding='utf-8') + queue_contract_source = Path('tests/test_required_workflow_queue_contract.py').read_text(encoding='utf-8') + rollout_source = Path('docs/org-required-workflow-rollout.md').read_text(encoding='utf-8') + assert '- cron: "0 * * * *"' in workflow_source + assert '*/15 * * * *' not in workflow_source + assert workflow_source.count('$(date -u +%s) / 3600') == 2 + assert "github.event.schedule == '0 * * * *'" in workflow_source + assert "github.event.schedule != '0 * * * *'" in workflow_source + assert '*/15 * * * *' not in queue_contract_source + assert '*/15 * * * *' not in rollout_source + PY + git diff --check + + - name: Commit repair and retire source-fix workflow + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git rm .github/workflows/repair_actions_queue_cadence.yml + git add \ + .github/workflows/pr-review-merge-scheduler.yml \ + tests/test_required_workflow_queue_contract.py \ + docs/org-required-workflow-rollout.md + git diff --cached --check + git commit -m "fix(scheduler): bound organization sweep to hourly cadence" + git push origin HEAD:fix/actions-queue-saturation-scheduler-cadence-20260902 From 1accd90210cf766f967eee008288b34e775e88fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:43:46 +0900 Subject: [PATCH 06/16] fix(queue): run cadence regression without undeclared pytest --- .github/workflows/repair_actions_queue_cadence.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/repair_actions_queue_cadence.yml b/.github/workflows/repair_actions_queue_cadence.yml index 6a5c1f31b4..cd9a072798 100644 --- a/.github/workflows/repair_actions_queue_cadence.yml +++ b/.github/workflows/repair_actions_queue_cadence.yml @@ -87,9 +87,19 @@ jobs: shell: bash run: | set -euo pipefail - python -m pytest -q tests/test_actions_queue_saturation_scheduler_cadence.py python - <<'PY' from pathlib import Path + import runpy + + contract_scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') + contract_tests = [ + contract_scope['test_org_queue_sweep_is_hourly_not_quarter_hourly'], + contract_scope['test_org_queue_sweep_wall_clock_fallback_matches_hourly_cadence'], + contract_scope['test_repository_scheduler_keeps_event_driven_wakes'], + ] + for contract_test in contract_tests: + contract_test() + workflow_source = Path('.github/workflows/pr-review-merge-scheduler.yml').read_text(encoding='utf-8') queue_contract_source = Path('tests/test_required_workflow_queue_contract.py').read_text(encoding='utf-8') rollout_source = Path('docs/org-required-workflow-rollout.md').read_text(encoding='utf-8') From edbc623f9653c7427868fe08c8ebf049d85d96c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:44:29 +0000 Subject: [PATCH 07/16] fix(scheduler): bound organization sweep to hourly cadence --- .../workflows/pr-review-merge-scheduler.yml | 16 +-- .../repair_actions_queue_cadence.yml | 129 ------------------ docs/org-required-workflow-rollout.md | 4 +- .../test_required_workflow_queue_contract.py | 10 +- 4 files changed, 15 insertions(+), 144 deletions(-) delete mode 100644 .github/workflows/repair_actions_queue_cadence.yml diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index b3deb32eef..e032acd9a2 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -74,15 +74,15 @@ on: type: string schedule: - cron: "*/30 * * * *" - # Every-15-minutes org-wide sweep cadence for the org-queue-sweep job below. Target + # Hourly org-wide sweep cadence for the org-queue-sweep job below. Target # repositories only receive scheduler runs on PR events, review/security # workflow completion, and protected-branch pushes; a PR whose approval or # required checks land AFTER its last event has no later trigger and sits # approved-but-unmerged until a human pushes something. The sweep closes - # that gap on a fixed heartbeat. Runs every 15 minutes so an approval or + # that gap on a fixed heartbeat. Runs hourly so an approval or # required check that lands after a PR's last event is auto-updated/merged - # within ~15 minutes instead of sitting idle for up to an hour. - - cron: "*/15 * * * *" + # within about an hour without adding quarter-hourly runner pressure. + - cron: "0 * * * *" repository_dispatch: types: [merge-scheduler] @@ -136,7 +136,7 @@ jobs: ) && ( github.event_name != 'schedule' || - github.event.schedule != '*/15 * * * *' + github.event.schedule != '0 * * * *' ) && ( github.event_name != 'repository_dispatch' || @@ -587,7 +587,7 @@ jobs: if: >- github.repository == 'ContextualWisdomLab/.github' && ( - (github.event_name == 'schedule' && github.event.schedule == '*/15 * * * *') || + (github.event_name == 'schedule' && github.event.schedule == '0 * * * *') || (github.event_name == 'repository_dispatch' && github.event.client_payload.org_sweep == true) ) runs-on: ubuntu-24.04 @@ -917,7 +917,7 @@ jobs: ORG_SWEEP_ROTATION_INDEX="$counter_next" else echo "::warning::read ${counter_variable_name}=${counter_current} but could not PATCH it; falling back to a wall-clock rotation tick for this run only" - ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 900 )) + ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 3600 )) fi elif gh api "repos/${GITHUB_REPOSITORY}/actions/variables" \ -X POST -f "name=${counter_variable_name}" -f "value=1" >/dev/null 2>&1; then @@ -930,7 +930,7 @@ jobs: ORG_SWEEP_ROTATION_INDEX=1 else echo "::warning::could not read/write ${counter_variable_name}; falling back to a wall-clock rotation tick for this run only" - ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 900 )) + ORG_SWEEP_ROTATION_INDEX=$(( $(date -u +%s) / 3600 )) fi fi if ! [[ "$ORG_SWEEP_ROTATION_INDEX" =~ ^[0-9]+$ ]]; then diff --git a/.github/workflows/repair_actions_queue_cadence.yml b/.github/workflows/repair_actions_queue_cadence.yml deleted file mode 100644 index cd9a072798..0000000000 --- a/.github/workflows/repair_actions_queue_cadence.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Repair Actions queue scheduler cadence - -on: - push: - branches: - - fix/actions-queue-saturation-scheduler-cadence-20260902 - -permissions: - contents: write - -concurrency: - group: actions-queue-cadence-repair-${{ github.ref }} - cancel-in-progress: true - -jobs: - repair_scheduler_cadence: - if: ${{ github.actor != 'github-actions[bot]' }} - runs-on: ubuntu-24.04 - steps: - - name: Check out exact canonical branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/actions-queue-saturation-scheduler-cadence-20260902 - fetch-depth: 0 - - - name: Apply bounded hourly sweep repair - shell: bash - run: | - set -euo pipefail - python - <<'PY' - from pathlib import Path - - scheduler_path = Path('.github/workflows/pr-review-merge-scheduler.yml') - scheduler_source = scheduler_path.read_text(encoding='utf-8') - old_cron = '*/15 * * * *' - hourly_cron = '0 * * * *' - assert scheduler_source.count(old_cron) >= 3 - assert scheduler_source.count('$(date -u +%s) / 900') == 2 - scheduler_source = scheduler_source.replace(old_cron, hourly_cron) - scheduler_source = scheduler_source.replace( - 'Every-15-minutes org-wide sweep cadence', - 'Hourly org-wide sweep cadence', - ) - scheduler_source = scheduler_source.replace( - 'Runs every 15 minutes so an approval or', - 'Runs hourly so an approval or', - ) - scheduler_source = scheduler_source.replace( - "within ~15 minutes instead of sitting idle for up to an hour.", - 'within about an hour without adding quarter-hourly runner pressure.', - ) - scheduler_source = scheduler_source.replace( - 'one latest pending 0 * * * * sweep', - 'one latest pending hourly sweep', - ) - scheduler_source = scheduler_source.replace( - '$(date -u +%s) / 900', - '$(date -u +%s) / 3600', - ) - assert old_cron not in scheduler_source - assert scheduler_source.count(hourly_cron) >= 3 - assert scheduler_source.count('$(date -u +%s) / 3600') == 2 - scheduler_path.write_text(scheduler_source, encoding='utf-8') - - queue_contract_path = Path('tests/test_required_workflow_queue_contract.py') - queue_contract_source = queue_contract_path.read_text(encoding='utf-8') - assert queue_contract_source.count(old_cron) >= 2 - queue_contract_source = queue_contract_source.replace(old_cron, hourly_cron) - queue_contract_source = queue_contract_source.replace( - 'every 15 minutes so an approval that lands after a PR\'s last event is', - 'hourly so an approval that lands after a PR\'s last event is', - ) - assert old_cron not in queue_contract_source - queue_contract_path.write_text(queue_contract_source, encoding='utf-8') - - rollout_path = Path('docs/org-required-workflow-rollout.md') - rollout_source = rollout_path.read_text(encoding='utf-8') - assert old_cron in rollout_source - rollout_source = rollout_source.replace(old_cron, hourly_cron) - rollout_source = rollout_source.replace('runs every 15 minutes', 'runs hourly') - rollout_source = rollout_source.replace('at most 15 minutes old', 'at most one hour old') - assert old_cron not in rollout_source - rollout_path.write_text(rollout_source, encoding='utf-8') - PY - - - name: Verify focused executable contracts - shell: bash - run: | - set -euo pipefail - python - <<'PY' - from pathlib import Path - import runpy - - contract_scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') - contract_tests = [ - contract_scope['test_org_queue_sweep_is_hourly_not_quarter_hourly'], - contract_scope['test_org_queue_sweep_wall_clock_fallback_matches_hourly_cadence'], - contract_scope['test_repository_scheduler_keeps_event_driven_wakes'], - ] - for contract_test in contract_tests: - contract_test() - - workflow_source = Path('.github/workflows/pr-review-merge-scheduler.yml').read_text(encoding='utf-8') - queue_contract_source = Path('tests/test_required_workflow_queue_contract.py').read_text(encoding='utf-8') - rollout_source = Path('docs/org-required-workflow-rollout.md').read_text(encoding='utf-8') - assert '- cron: "0 * * * *"' in workflow_source - assert '*/15 * * * *' not in workflow_source - assert workflow_source.count('$(date -u +%s) / 3600') == 2 - assert "github.event.schedule == '0 * * * *'" in workflow_source - assert "github.event.schedule != '0 * * * *'" in workflow_source - assert '*/15 * * * *' not in queue_contract_source - assert '*/15 * * * *' not in rollout_source - PY - git diff --check - - - name: Commit repair and retire source-fix workflow - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git rm .github/workflows/repair_actions_queue_cadence.yml - git add \ - .github/workflows/pr-review-merge-scheduler.yml \ - tests/test_required_workflow_queue_contract.py \ - docs/org-required-workflow-rollout.md - git diff --cached --check - git commit -m "fix(scheduler): bound organization sweep to hourly cadence" - git push origin HEAD:fix/actions-queue-saturation-scheduler-cadence-20260902 diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 36edcd29dd..7c55c6fbab 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -156,9 +156,9 @@ The central `.github/workflows/pr-review-merge-scheduler.yml` is now part of the Do not centralize the scheduler by running a `.github` scheduled job against other repositories with the `.github` repository token. That would either fail permission checks or use the wrong mutation actor. The central path is a required workflow executed in each target repository context. -- Heartbeat fallback posture: event-driven target-repository runs stop retrying once their triggering event is consumed, so a PR that becomes mergeable AFTER its last event (approval published after the scheduler pass, merge-preview checks landing late, a temporary base-branch policy blocker clearing) has no later trigger and sits approved-but-unmerged. The `org-queue-sweep` job in the central scheduler workflow closes this gap: it runs every 15 minutes (`*/15 * * * *`) only in `ContextualWisdomLab/.github`, re-runs the same trusted scheduler script against every non-archived organization repository, and merges/updates through the identical guarded contract. Stacked PRs, which do not receive injected required workflows, use a separate bounded OpenCode dispatch budget so ordinary default-branch traffic cannot leave them at `OpenCode review absent`. It never uses the `.github` repository `github.token` for sibling mutations — it requires `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, or the exchanged OpenCode app token, and fails with a visible `::error` reason when no cross-repository mutation credential is available instead of silently no-opping. Every swept repository prints its per-PR decision log, so an unmerged PR always has a concrete logged reason at most 15 minutes old. +- Heartbeat fallback posture: event-driven target-repository runs stop retrying once their triggering event is consumed, so a PR that becomes mergeable AFTER its last event (approval published after the scheduler pass, merge-preview checks landing late, a temporary base-branch policy blocker clearing) has no later trigger and sits approved-but-unmerged. The `org-queue-sweep` job in the central scheduler workflow closes this gap: it runs hourly (`0 * * * *`) only in `ContextualWisdomLab/.github`, re-runs the same trusted scheduler script against every non-archived organization repository, and merges/updates through the identical guarded contract. Stacked PRs, which do not receive injected required workflows, use a separate bounded OpenCode dispatch budget so ordinary default-branch traffic cannot leave them at `OpenCode review absent`. It never uses the `.github` repository `github.token` for sibling mutations — it requires `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, or the exchanged OpenCode app token, and fails with a visible `::error` reason when no cross-repository mutation credential is available instead of silently no-opping. Every swept repository prints its per-PR decision log, so an unmerged PR always has a concrete logged reason at most one hour old. - Queue hygiene posture: during the sweep, workflow runs still `queued` after `ORG_SWEEP_STALE_QUEUE_HOURS` (default 24h) are cancelled with their run id, workflow name, head branch, and age logged. A run queued that long belongs to a head that PR events will never revisit (closed PR, force-pushed branch, or a previous runner outage), and leaving it keeps the Actions queue holding non-current-head work. -- Inaccessible-repository posture: a sibling repository the sweep credential structurally cannot read — the OpenCode app is not installed there, or `PR_REVIEW_MERGE_TOKEN` does not cover it — returns HTTP 403 `Resource not accessible by integration` on every read. That is an access-grant fact the automation can never resolve, so the sweep classifies it as a skipped, non-fatal **unavailable** repository (a `::warning` naming the repository and the remediation) instead of a hard failure. Without this, a handful of un-enrolled repositories keeps the scheduled sweep heartbeat (the org sweep's `*/15 * * * *` cron) permanently red and masks a genuinely new repository that starts failing. Fail-closed is preserved on both sides: any non-403 scheduler failure still fails the sweep with its per-PR reason, and if more than `ORG_SWEEP_MAX_UNAVAILABLE` (default 5) repositories become unreachable in one pass — a credential-scope regression rather than a few un-enrolled repos — the job fails loudly. Remediation for a listed repository is to install the OpenCode app on it or grant `PR_REVIEW_MERGE_TOKEN` access. +- Inaccessible-repository posture: a sibling repository the sweep credential structurally cannot read — the OpenCode app is not installed there, or `PR_REVIEW_MERGE_TOKEN` does not cover it — returns HTTP 403 `Resource not accessible by integration` on every read. That is an access-grant fact the automation can never resolve, so the sweep classifies it as a skipped, non-fatal **unavailable** repository (a `::warning` naming the repository and the remediation) instead of a hard failure. Without this, a handful of un-enrolled repositories keeps the scheduled sweep heartbeat (the org sweep's `0 * * * *` cron) permanently red and masks a genuinely new repository that starts failing. Fail-closed is preserved on both sides: any non-403 scheduler failure still fails the sweep with its per-PR reason, and if more than `ORG_SWEEP_MAX_UNAVAILABLE` (default 5) repositories become unreachable in one pass — a credential-scope regression rather than a few un-enrolled repos — the job fails loudly. Remediation for a listed repository is to install the OpenCode app on it or grant `PR_REVIEW_MERGE_TOKEN` access. ## Second-reviewer (Noema) posture diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 9823c417c1..e18e021fae 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -967,7 +967,7 @@ def test_org_queue_sweep_covers_target_repositories_on_a_heartbeat() -> None: cron, use a cross-repository mutation credential (never the repository github.token silently), skip the central repository itself, and fail with a visible reason when it cannot mutate sibling repositories. The sweep runs - every 15 minutes so an approval that lands after a PR's last event is + hourly so an approval that lands after a PR's last event is auto-updated/merged promptly instead of idling indefinitely. Its cron has a distinct concurrency key from the separate 30-minute scan, and the job has enough runtime headroom to finish a complete organization walk. @@ -975,9 +975,9 @@ def test_org_queue_sweep_covers_target_repositories_on_a_heartbeat() -> None: workflow = workflow_text("pr-review-merge-scheduler.yml") assert "org-queue-sweep:" in workflow - assert '- cron: "*/15 * * * *"' in workflow + assert '- cron: "0 * * * *"' in workflow assert "github.repository == 'ContextualWisdomLab/.github'" in workflow - assert "github.event.schedule == '*/15 * * * *'" in workflow + assert "github.event.schedule == '0 * * * *'" in workflow assert "github.event.client_payload.org_sweep == true" in workflow assert ( "github.event_name == 'schedule' && format('schedule-{0}', " @@ -994,7 +994,7 @@ def test_org_queue_sweep_covers_target_repositories_on_a_heartbeat() -> None: ): assert f"{setting}: ${{{{ github.event_name == 'schedule' ||" in workflow # The single-repository scan must not double-run on the sweep cron. - assert "github.event.schedule != '*/15 * * * *'" in workflow + assert "github.event.schedule != '0 * * * *'" in workflow assert "github.event.client_payload.org_sweep != true" in workflow # The sweep must never silently no-op with the repository-scoped token. assert ( @@ -1494,7 +1494,7 @@ def test_org_queue_sweep_treats_inaccessible_repositories_as_non_fatal() -> None automation can never resolve, so those repositories are reported as skipped, non-fatal "unavailable" repositories rather than hard failures — otherwise a handful of un-enrolled repositories keeps the scheduled sweep (the - ``*/15 * * * *`` cron) permanently red and masks a genuinely new repository + ``0 * * * *`` cron) permanently red and masks a genuinely new repository that starts failing. The sweep stays fail-closed two ways: any non-403 scheduler failure still From c02580b9bb577c4f2ab0c2cb29c52f32385f3551 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:46:00 +0900 Subject: [PATCH 08/16] chore(ci): retire PR 1630 source-fix helper --- .../repair-pr1630-scheduler-cadence.yml | 124 ------------------ 1 file changed, 124 deletions(-) delete mode 100644 .github/workflows/repair-pr1630-scheduler-cadence.yml diff --git a/.github/workflows/repair-pr1630-scheduler-cadence.yml b/.github/workflows/repair-pr1630-scheduler-cadence.yml deleted file mode 100644 index 11ee9bb1b7..0000000000 --- a/.github/workflows/repair-pr1630-scheduler-cadence.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Repair PR 1630 scheduler cadence - -on: - push: - branches: - - fix/actions-queue-saturation-scheduler-cadence-20260902 - paths: - - .github/workflows/repair-pr1630-scheduler-cadence.yml - -permissions: {} - -jobs: - repair: - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - name: Harden runner - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 - with: - egress-policy: audit - disable-file-monitoring: true - - - name: Checkout exact writer head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 2 - - - name: Materialize and verify hourly sweep repair - shell: bash - env: - WRITER_BRANCH: fix/actions-queue-saturation-scheduler-cadence-20260902 - EXPECTED_HEAD: ${{ github.sha }} - run: | - set -euo pipefail - remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" - test "$remote_head" = "$EXPECTED_HEAD" - test "$(git rev-parse HEAD)" = "$EXPECTED_HEAD" - - python3 - <<'PY' - from pathlib import Path - - workflow_path = Path('.github/workflows/pr-review-merge-scheduler.yml') - workflow = workflow_path.read_text(encoding='utf-8') - assert workflow.count('- cron: "*/15 * * * *"') == 1 - assert workflow.count("github.event.schedule == '*/15 * * * *'") == 1 - assert workflow.count('$(date -u +%s) / 900') == 2 - workflow = workflow.replace('Every-15-minutes org-wide sweep cadence', 'Hourly org-wide sweep cadence', 1) - workflow = workflow.replace('Runs every 15 minutes so an approval or', 'Runs hourly so an approval or', 1) - workflow = workflow.replace('within ~15 minutes instead of sitting idle for up to an hour.', 'within ~1 hour instead of sitting idle indefinitely.', 1) - workflow = workflow.replace('- cron: "*/15 * * * *"', '- cron: "0 * * * *"', 1) - workflow = workflow.replace("github.event.schedule == '*/15 * * * *'", "github.event.schedule == '0 * * * *'", 1) - workflow = workflow.replace('one running and one latest pending */15 sweep', 'one running and one latest pending hourly sweep', 1) - workflow = workflow.replace('900s', '3600s') - workflow = workflow.replace('$(date -u +%s) / 900', '$(date -u +%s) / 3600') - assert '*/15 * * * *' not in workflow - assert workflow.count('$(date -u +%s) / 3600') == 2 - workflow_path.write_text(workflow, encoding='utf-8') - - contract_path = Path('tests/test_required_workflow_queue_contract.py') - contract = contract_path.read_text(encoding='utf-8') - assert contract.count('- cron: "*/15 * * * *"') == 1 - assert contract.count("github.event.schedule == '*/15 * * * *'") == 1 - contract = contract.replace('every 15 minutes so an approval', 'hourly so an approval', 1) - contract = contract.replace('- cron: "*/15 * * * *"', '- cron: "0 * * * *"', 1) - contract = contract.replace("github.event.schedule == '*/15 * * * *'", "github.event.schedule == '0 * * * *'", 1) - assert '*/15 * * * *' not in contract - contract_path.write_text(contract, encoding='utf-8') - - docs_path = Path('docs/org-required-workflow-rollout.md') - docs = docs_path.read_text(encoding='utf-8') - assert '*/15 * * * *' in docs - docs = docs.replace('*/15 * * * *', '0 * * * *') - docs = docs.replace('every 15 minutes', 'hourly') - docs = docs.replace('Every 15 minutes', 'Every hour') - docs = docs.replace('at most 15 minutes old', 'at most one hour old') - docs = docs.replace('within 15 minutes', 'within one hour') - docs = docs.replace('15-minute org sweep', 'hourly org sweep') - docs = docs.replace('15-minute sweep', 'hourly sweep') - docs = docs.replace('15-minute cadence', 'hourly cadence') - assert '*/15 * * * *' not in docs - docs_path.write_text(docs, encoding='utf-8') - PY - - python3 - <<'PY' - import runpy - scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') - for name, value in sorted(scope.items()): - if name.startswith('test_') and callable(value): - value() - print(f'PASS {name}') - PY - - python3 - <<'PY' - from pathlib import Path - workflow = Path('.github/workflows/pr-review-merge-scheduler.yml').read_text(encoding='utf-8') - contract = Path('tests/test_required_workflow_queue_contract.py').read_text(encoding='utf-8') - assert '- cron: "*/30 * * * *"' in workflow - assert '- cron: "0 * * * *"' in workflow - assert "github.event.schedule == '0 * * * *'" in workflow - assert "pull_request_target:" in workflow - assert "pull_request_review:" in workflow - assert "workflow_run:" in workflow - assert "repository_dispatch:" in workflow - assert 'org-queue-sweep:' in workflow - assert '- cron: "0 * * * *"' in contract - assert "github.event.schedule == '0 * * * *'" in contract - assert workflow.count('$(date -u +%s) / 3600') == 2 - print('PASS required scheduler queue cadence contract') - PY - - git diff --check - rm .github/workflows/repair-pr1630-scheduler-cadence.yml - git add .github/workflows/pr-review-merge-scheduler.yml tests/test_actions_queue_saturation_scheduler_cadence.py tests/test_required_workflow_queue_contract.py docs/org-required-workflow-rollout.md .github/workflows/repair-pr1630-scheduler-cadence.yml - git diff --cached --check - - remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" - test "$remote_head" = "$EXPECTED_HEAD" - - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git commit -m "fix(scheduler): reduce org sweep pressure under saturation" - git push origin "HEAD:${WRITER_BRANCH}" From dee73f5e98d7fd1edf318a8ed86cd901f4cf6460 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:47:16 +0900 Subject: [PATCH 09/16] test(scheduler): reject stale quarter-hour rotation comments --- tests/test_actions_queue_saturation_scheduler_cadence.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_actions_queue_saturation_scheduler_cadence.py b/tests/test_actions_queue_saturation_scheduler_cadence.py index 17ce01f4b4..fbf8f45547 100644 --- a/tests/test_actions_queue_saturation_scheduler_cadence.py +++ b/tests/test_actions_queue_saturation_scheduler_cadence.py @@ -15,10 +15,13 @@ def test_org_queue_sweep_is_hourly_not_quarter_hourly() -> None: def test_org_queue_sweep_wall_clock_fallback_matches_hourly_cadence() -> None: - """Fallback rotation must advance once per hourly sweep, not four offsets at once.""" + """Fallback rotation and its maintenance comments must match hourly cadence.""" workflow = WORKFLOW.read_text(encoding="utf-8") assert workflow.count("$(date -u +%s) / 3600") == 2 assert "$(date -u +%s) / 900" not in workflow + assert "900s window" not in workflow + assert "900s)" not in workflow + assert "pending */15 sweep" not in workflow def test_repository_scheduler_keeps_event_driven_wakes() -> None: From b1c6b4a1b19a08cc98be41bdae7691cc10f48878 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:47:29 +0900 Subject: [PATCH 10/16] chore(ci): materialize PR 1630 comment repair --- .../repair-pr1630-hourly-comments.yml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/repair-pr1630-hourly-comments.yml diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml new file mode 100644 index 0000000000..063357679e --- /dev/null +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -0,0 +1,72 @@ +name: Repair PR 1630 hourly cadence comments + +on: + push: + branches: + - fix/actions-queue-saturation-scheduler-cadence-20260902 + paths: + - .github/workflows/repair-pr1630-hourly-comments.yml + +permissions: + contents: write + +jobs: + repair: + runs-on: ubuntu-24.04 + steps: + - name: Check out exact writer head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 2 + + - name: Repair comments, verify contract, and retire helper + shell: bash + env: + WRITER_BRANCH: fix/actions-queue-saturation-scheduler-cadence-20260902 + EXPECTED_HEAD: ${{ github.sha }} + run: | + set -euo pipefail + remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" + test "$remote_head" = "$EXPECTED_HEAD" + test "$(git rev-parse HEAD)" = "$EXPECTED_HEAD" + + python3 - <<'PY' + from pathlib import Path + path = Path('.github/workflows/pr-review-merge-scheduler.yml') + source = path.read_text(encoding='utf-8') + replacements = { + 'one latest pending */15 sweep': 'one latest pending hourly sweep', + 'one 900s window elapse': 'one hourly window elapse', + 'wall-clock tick (one per 900s)': 'wall-clock tick (one per hour)', + } + for old, new in replacements.items(): + assert source.count(old) == 1, old + source = source.replace(old, new) + assert 'pending */15 sweep' not in source + assert '900s window' not in source + assert '900s)' not in source + assert source.count('$(date -u +%s) / 3600') == 2 + assert '*/15 * * * *' not in source + path.write_text(source, encoding='utf-8') + PY + + python3 - <<'PY' + import runpy + scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') + for name, value in sorted(scope.items()): + if name.startswith('test_') and callable(value): + value() + print(f'PASS {name}') + PY + git diff --check + + git rm .github/workflows/repair-pr1630-hourly-comments.yml + git add .github/workflows/pr-review-merge-scheduler.yml tests/test_actions_queue_saturation_scheduler_cadence.py + git diff --cached --check + remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" + test "$remote_head" = "$EXPECTED_HEAD" + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git commit -m "docs(scheduler): align rotation comments with hourly fallback" + git push origin "HEAD:${WRITER_BRANCH}" From 8ae6ba9ab0d787f693551ce6ba97bc974673bc11 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:49:12 +0900 Subject: [PATCH 11/16] fix(ci): account for both stale rotation-window comments --- .../workflows/repair-pr1630-hourly-comments.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml index 063357679e..2d84eabe89 100644 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -35,13 +35,14 @@ jobs: from pathlib import Path path = Path('.github/workflows/pr-review-merge-scheduler.yml') source = path.read_text(encoding='utf-8') - replacements = { - 'one latest pending */15 sweep': 'one latest pending hourly sweep', - 'one 900s window elapse': 'one hourly window elapse', - 'wall-clock tick (one per 900s)': 'wall-clock tick (one per hour)', - } - for old, new in replacements.items(): - assert source.count(old) == 1, old + replacements = ( + ('one latest pending */15 sweep', 'one latest pending hourly sweep', 1), + ('one 900s window elapse', 'one hourly window elapse', 2), + ('wall-clock tick (one per 900s)', 'wall-clock tick (one per hour)', 1), + ) + for old, new, expected_count in replacements: + actual_count = source.count(old) + assert actual_count == expected_count, (old, actual_count, expected_count) source = source.replace(old, new) assert 'pending */15 sweep' not in source assert '900s window' not in source From a15bd155ead282803cef59c707a5bc6d21dd89ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:52:21 +0900 Subject: [PATCH 12/16] fix(queue): use workflow-capable writer for helper retirement --- .github/workflows/repair-pr1630-hourly-comments.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml index 2d84eabe89..d4ddc4cf5b 100644 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -8,10 +8,10 @@ on: - .github/workflows/repair-pr1630-hourly-comments.yml permissions: - contents: write + contents: read jobs: - repair: + repair_scheduler_comments: runs-on: ubuntu-24.04 steps: - name: Check out exact writer head @@ -19,6 +19,7 @@ jobs: with: ref: ${{ github.sha }} fetch-depth: 2 + token: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }} - name: Repair comments, verify contract, and retire helper shell: bash @@ -55,10 +56,10 @@ jobs: python3 - <<'PY' import runpy scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') - for name, value in sorted(scope.items()): - if name.startswith('test_') and callable(value): - value() - print(f'PASS {name}') + for test_name, contract_test in sorted(scope.items()): + if test_name.startswith('test_') and callable(contract_test): + contract_test() + print(f'PASS {test_name}') PY git diff --check From 45ff813749784973c717d67e740bfc8c19e0317f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:54:38 +0900 Subject: [PATCH 13/16] fix(ci): retrigger PR 1630 repair on every branch advance --- .github/workflows/repair-pr1630-hourly-comments.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml index d4ddc4cf5b..d6f171a497 100644 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -4,8 +4,6 @@ on: push: branches: - fix/actions-queue-saturation-scheduler-cadence-20260902 - paths: - - .github/workflows/repair-pr1630-hourly-comments.yml permissions: contents: read From 5664b5a35a2b0588321d0bbd4976ecb49c28c648 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:56:52 +0900 Subject: [PATCH 14/16] fix(ci): grant PR 1630 repair job scoped write --- .github/workflows/repair-pr1630-hourly-comments.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml index d6f171a497..3e44a79365 100644 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -10,6 +10,8 @@ permissions: jobs: repair_scheduler_comments: + permissions: + contents: write runs-on: ubuntu-24.04 steps: - name: Check out exact writer head From 0243d67cec8a565b3a7f9b23e1bd3535ab6bae15 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:58:04 +0900 Subject: [PATCH 15/16] fix(scheduler): make hourly comment repair supersession-safe --- .github/workflows/repair-pr1630-hourly-comments.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml index 3e44a79365..cd80838808 100644 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ b/.github/workflows/repair-pr1630-hourly-comments.yml @@ -5,6 +5,10 @@ on: branches: - fix/actions-queue-saturation-scheduler-cadence-20260902 +concurrency: + group: repair-pr1630-hourly-comments-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read From 141d5827ecfaf5bcaa0d750df430574651d01dc9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:58:34 +0000 Subject: [PATCH 16/16] docs(scheduler): align rotation comments with hourly fallback --- .../workflows/pr-review-merge-scheduler.yml | 8 +- .../repair-pr1630-hourly-comments.yml | 78 ------------------- 2 files changed, 4 insertions(+), 82 deletions(-) delete mode 100644 .github/workflows/repair-pr1630-hourly-comments.yml diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index e032acd9a2..a15cdf36e1 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -592,7 +592,7 @@ jobs: ) runs-on: ubuntu-24.04 # The complete organization walk exceeded the legacy 30-minute boundary in - # production. Keep one running and one latest pending */15 sweep through the + # production. Keep one running and one latest pending hourly sweep through the # schedule-specific concurrency key above, while allowing the current walk # enough time to finish instead of cancelling before later repositories. timeout-minutes: 60 @@ -636,7 +636,7 @@ jobs: # ticks" guarantee a rotation is meant to provide. Wall-clock time alone # is also insufficient, since this single-flight/non-cancelling job can # run up to 60 minutes and a delayed real execution can let more than - # one 900s window elapse, occasionally repeating a modulo offset + # one hourly window elapse, occasionally repeating a modulo offset # (ContextualWisdomLab/.github#1223 review finding). # A repository the sweep credential structurally cannot read (the OpenCode # app is not installed there / the PR_REVIEW_MERGE_TOKEN lacks it) returns @@ -850,10 +850,10 @@ jobs: # source: a persistent `ORG_SWEEP_ROTATION_COUNTER` repository # variable on this (.github) repository, incremented by exactly # one at the start of every actual org-queue-sweep execution. A - # wall-clock tick (one per 900s) is *not* sufficient on its own: + # wall-clock tick (one per hour) is *not* sufficient on its own: # this job is single-flight/non-cancelling with up to a 60-minute # timeout, so a delayed or backlogged execution can let more than - # one 900s window elapse between two real sweep runs, and if that + # one hourly window elapse between two real sweep runs, and if that # gap happens to be an exact multiple of the repository count the # modulo offset repeats -- reintroducing the exact starvation # #1220 fixed (CodeRabbit review finding on #1223). A persistent diff --git a/.github/workflows/repair-pr1630-hourly-comments.yml b/.github/workflows/repair-pr1630-hourly-comments.yml deleted file mode 100644 index cd80838808..0000000000 --- a/.github/workflows/repair-pr1630-hourly-comments.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Repair PR 1630 hourly cadence comments - -on: - push: - branches: - - fix/actions-queue-saturation-scheduler-cadence-20260902 - -concurrency: - group: repair-pr1630-hourly-comments-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - repair_scheduler_comments: - permissions: - contents: write - runs-on: ubuntu-24.04 - steps: - - name: Check out exact writer head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 2 - token: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }} - - - name: Repair comments, verify contract, and retire helper - shell: bash - env: - WRITER_BRANCH: fix/actions-queue-saturation-scheduler-cadence-20260902 - EXPECTED_HEAD: ${{ github.sha }} - run: | - set -euo pipefail - remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" - test "$remote_head" = "$EXPECTED_HEAD" - test "$(git rev-parse HEAD)" = "$EXPECTED_HEAD" - - python3 - <<'PY' - from pathlib import Path - path = Path('.github/workflows/pr-review-merge-scheduler.yml') - source = path.read_text(encoding='utf-8') - replacements = ( - ('one latest pending */15 sweep', 'one latest pending hourly sweep', 1), - ('one 900s window elapse', 'one hourly window elapse', 2), - ('wall-clock tick (one per 900s)', 'wall-clock tick (one per hour)', 1), - ) - for old, new, expected_count in replacements: - actual_count = source.count(old) - assert actual_count == expected_count, (old, actual_count, expected_count) - source = source.replace(old, new) - assert 'pending */15 sweep' not in source - assert '900s window' not in source - assert '900s)' not in source - assert source.count('$(date -u +%s) / 3600') == 2 - assert '*/15 * * * *' not in source - path.write_text(source, encoding='utf-8') - PY - - python3 - <<'PY' - import runpy - scope = runpy.run_path('tests/test_actions_queue_saturation_scheduler_cadence.py') - for test_name, contract_test in sorted(scope.items()): - if test_name.startswith('test_') and callable(contract_test): - contract_test() - print(f'PASS {test_name}') - PY - git diff --check - - git rm .github/workflows/repair-pr1630-hourly-comments.yml - git add .github/workflows/pr-review-merge-scheduler.yml tests/test_actions_queue_saturation_scheduler_cadence.py - git diff --cached --check - remote_head="$(git ls-remote origin "refs/heads/${WRITER_BRANCH}" | awk '{print $1}')" - test "$remote_head" = "$EXPECTED_HEAD" - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git commit -m "docs(scheduler): align rotation comments with hourly fallback" - git push origin "HEAD:${WRITER_BRANCH}"