diff --git a/.github/workflows/branch-preview.yaml b/.github/workflows/branch-preview.yaml index f870dec9..f874cae6 100644 --- a/.github/workflows/branch-preview.yaml +++ b/.github/workflows/branch-preview.yaml @@ -324,7 +324,11 @@ jobs: cache: ${{ vars.FORGE_CI_RUNNER == '' }} - name: test run: | - go test ./... + # -timeout 30m — see the matching comment on branch.yaml's + # `test` step. Default 10m / test-binary leaves no room when + # pkg/e2e/scopedstore has to cold-fetch the ~230MB + # sc-plugin-gcp binary mid-test. + go test -timeout 30m ./... docker-build: name: Docker build and push preview ${{ matrix.image }} image diff --git a/.github/workflows/branch.yaml b/.github/workflows/branch.yaml index 98a2ca84..691d2d25 100644 --- a/.github/workflows/branch.yaml +++ b/.github/workflows/branch.yaml @@ -383,7 +383,15 @@ jobs: cache: ${{ vars.FORGE_CI_RUNNER == '' }} - name: test run: | - go test ./... + # -timeout 30m: default is 10m per test binary. The e2e package + # pkg/e2e/scopedstore spawns the pinned sc-plugin-gcp binary + # (230MB+) on cold-cache first-fetch, which eats several minutes + # of the 10m budget by itself and leaves Test_Provision no room + # to finish. Live-caught on PR #568 (2026-11-08): two consecutive + # reruns timed out mid-download, not in the test logic. 30m + # matches the already-bumped budget in test-registry-integration + # below. + go test -timeout 30m ./... test-registry-integration: name: Run integration tests diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 22176714..415b12e1 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -489,7 +489,11 @@ jobs: cache: ${{ vars.FORGE_CI_RUNNER == '' }} - name: test run: | - go test ./... + # -timeout 30m — see the matching comment on branch.yaml's + # `test` step. Default 10m / test-binary leaves no room when + # pkg/e2e/scopedstore has to cold-fetch the ~230MB + # sc-plugin-gcp binary mid-test. + go test -timeout 30m ./... build-docs: name: Build documentation diff --git a/pkg/plugin/fetcher.go b/pkg/plugin/fetcher.go index 98723d15..87b7bc78 100644 --- a/pkg/plugin/fetcher.go +++ b/pkg/plugin/fetcher.go @@ -201,8 +201,24 @@ func fileSHA256(path string) (string, error) { return hex.EncodeToString(h.Sum(nil)), nil } +// downloadFileHTTPTimeout bounds a single plugin binary download. Plugin +// binaries carry every cloud SDK a plugin depends on and run 180-280MB per +// platform-arch (sc-plugin-gcp is the current ceiling). GHA hosted runners +// see ~1-2 MB/s steady-state to GitHub releases on a cold download, so even +// the ceiling fits comfortably in 15 min. The old 5 min cutoff pinned us to +// a window that the 230MB sc-plugin-gcp binary brushed up against — CI +// e2e tests that re-fetch on every run hit `context deadline exceeded` even +// though the stream was making progress. Live-caught 2026-11-08 on api PR +// #568 (phase 5 of sc-plugin-gcp extraction): two consecutive reruns of +// TestScopeRegression_DamagedScopeFileFailsLintDeployAndReseal + Test_Provision +// timed out inside this call. See +// forge/docs/roadmap/items/sc-plugin-stack-outputs-latency-full-schema-load.md +// for the companion latency work (cache-aware load for read-only entries; +// this fix is orthogonal — about the first-ever fetch, not repeat lookups). +const downloadFileHTTPTimeout = 15 * time.Minute + func downloadFile(url, dst string) error { - client := &http.Client{Timeout: 5 * time.Minute} + client := &http.Client{Timeout: downloadFileHTTPTimeout} resp, err := client.Get(url) //nolint:noctx // allow — tool-level call if err != nil { return err