Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,15 @@ jobs:
# and all CI jobs share the 60-concurrent hosted cap: 13 in-flight
# runs at ~20 jobs held the queue at 0 merges in 40 min. Halving the
# gate's matrix halves its share of that cap.
slice_count: ${{ github.event_name == 'merge_group' && 8 || 16 }}
#
# 2026-09-25 (t_bf25c6b2): merge_group back to 16. At 8 each queue slice
# carried ~2x the files and ran 10-12 min, and heavy files sharing a
# loaded runner crossed the fixed 300 s per-file wall: 13 of 14 failed
# queue runs 15:00-20:30 PT were a per-file timeout (0 assertion
# failures), each ejecting its entry and cancelling the builds behind
# it. The hosted cap is 120 since 09-25, so the cap-share argument
# above no longer binds.
slice_count: 16
test_scope: ${{ needs.detect.outputs.test_scope }}

# macOS + Windows lanes. The main `tests` lane above is Linux-only, and
Expand Down
46 changes: 45 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ jobs:
restore-keys: |
test-durations-

- name: Restore duration history (per-file timeout budgets)
# Separate entry from the LPT cache above (t_bf25c6b2): the newest
# samples per file, whose p90 sets each file's measured timeout
# budget. Absent -> budgets fall back to the single cached value, then
# to the 300 s floor (today's behaviour).
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: test_durations_history.json
key: test-durations-history-restore-${{ github.run_id }}
restore-keys: |
test-durations-history-

- name: Generate test slices
id: matrix
env:
Expand Down Expand Up @@ -291,7 +303,10 @@ jobs:
run: |
source .venv/bin/activate
# Verbose pytest output lets timeout verdicts name the last test reached.
scripts/run_tests.sh --files '${{ matrix.slice.files }}' -- -v
# file_timeouts: measured per-file budgets stamped by the generate job
# (3x p90 duration, floor 300 s, cap 900 s; t_bf25c6b2). Empty for
# a slice with no heavy file, and for a matrix that lacks the field.
scripts/run_tests.sh --files '${{ matrix.slice.files }}' --file-timeouts '${{ matrix.slice.file_timeouts }}' -- -v
env:
# Ensure tests don't accidentally call real APIs
OPENROUTER_API_KEY: ""
Expand Down Expand Up @@ -330,6 +345,14 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# First, so checkout's clean cannot touch the downloaded durations.
- name: Checkout runner script
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
sparse-checkout: scripts/run_tests_parallel.py
sparse-checkout-cone-mode: false

- name: Download all slice durations
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -374,6 +397,27 @@ jobs:
path: test_durations.json
key: test-durations-${{ github.run_id }}

# Rolling per-file history for the measured timeout budgets (see the
# generate job): restore the newest entry, append this run, save anew.
# Same cache path as the generate job's restore (actions/cache versions
# entries by path).
- name: Restore previous duration history
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: test_durations_history.json
key: test-durations-history-restore-${{ github.run_id }}
restore-keys: |
test-durations-history-

- name: Append this run to the duration history
run: python3 scripts/run_tests_parallel.py --merge-duration-history test_durations.json

- name: Save duration history
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: test_durations_history.json
key: test-durations-history-${{ github.run_id }}

e2e:
needs: [generate, placement]
if: always() && !cancelled() && needs.generate.result == 'success'
Expand Down
Loading