diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0a923973..cd3c92ce 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -46,9 +46,7 @@ updates: # pre-release filter is bypassed for *grouped* updates # (dependabot-core#9496), so a grouped python update proposes pre-release # tags like python:3.15.0b2 as if they were a normal stable minor bump. - # Updated individually, python is filtered correctly: alpha/beta/rc tags - # are skipped and only stable releases (e.g. 3.15.0 once final) are - # proposed. node + uv stay grouped into a single digest PR. + # node + uv stay grouped into a single digest PR. patterns: ["*"] exclude-patterns: ["python"] ignore: @@ -58,6 +56,24 @@ updates: - dependency-name: "node" update-types: ["version-update:semver-major"] + # Python: block minor/major bumps. Ungrouping python (above) is NOT enough + # to keep pre-releases out — dependabot-core#13815 rewrote the Docker + # pre-release heuristic to catch PEP 440 tags like 3.15.0a2 / 3.5.0b3, but + # the suffixed real tag still slipped through as PR #1169 + # (python:3.14.6-slim -> python:3.15.0b3-slim). CPython spells + # pre-releases without a separator, so tag parsing reads 3.15.0b3 as an + # ordinary version that sorts above 3.14.6. + # + # A minor-version ignore blocks it regardless of spelling. Patch bumps + # (3.14.6 -> 3.14.7) and same-tag digest refreshes still land automatically. + # Moving the runtime to a new Python minor is a manual, deliberate change: + # bump the tag here and confirm C-extension wheels (greenlet/gevent) exist + # for it — a source build against a pre-release ABI boots an app that binds + # its port but never serves, which wedges e2e for the full 6h job limit. + - dependency-name: "python" + update-types: + ["version-update:semver-major", "version-update:semver-minor"] + # GitHub Actions - package-ecosystem: "github-actions" directory: "/" diff --git a/.github/workflows/e2e-platform.yml b/.github/workflows/e2e-platform.yml index 263f330a..30310bf2 100644 --- a/.github/workflows/e2e-platform.yml +++ b/.github/workflows/e2e-platform.yml @@ -60,6 +60,9 @@ jobs: e2e: needs: select-profiles runs-on: ubuntu-latest + # A wedged app under test must not burn GitHub's 6h max job limit. A healthy + # profile run finishes in ~3-5 min; anything past 25 is hung, not slow. + timeout-minutes: 25 strategy: fail-fast: false matrix: @@ -87,6 +90,8 @@ jobs: needs: changes if: needs.changes.outputs.relevant == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-latest + # Real Chrome + qBittorrent is the slowest profile; still nowhere near 40 min. + timeout-minutes: 40 name: e2e (full — real Chrome + qBittorrent) steps: - name: Checkout diff --git a/tests/e2e/platform/run-e2e.sh b/tests/e2e/platform/run-e2e.sh index c12e83ff..f3585636 100755 --- a/tests/e2e/platform/run-e2e.sh +++ b/tests/e2e/platform/run-e2e.sh @@ -59,8 +59,17 @@ fi echo "==> [$PROFILE] waiting for shelfmark health" HEALTHY=0 -for _ in $(seq 1 60); do - if curl -fsS http://localhost:8084/api/health >/dev/null 2>&1; then HEALTHY=1; break; fi +# The curl timeouts are load-bearing, not belt-and-braces. A container that +# binds 8084 but never answers (e.g. a broken C-extension wheel wedging the +# gunicorn worker) blocks a bare `curl` forever on read, so an iteration-counted +# loop never reaches iteration 2 and the wait becomes unbounded — that hung CI +# for the full 6h job limit on PR #1169. Bound each probe AND the whole wait. +HEALTH_DEADLINE=$((SECONDS + 120)) +while ((SECONDS < HEALTH_DEADLINE)); do + if curl -fsS --connect-timeout 3 --max-time 5 http://localhost:8084/api/health >/dev/null 2>&1; then + HEALTHY=1 + break + fi sleep 2 done