From 1f77a41a02a355f6d87f8f55d38b4ea1f309bf60 Mon Sep 17 00:00:00 2001 From: Starfolk Date: Fri, 1 May 2026 19:18:06 +0000 Subject: [PATCH 1/2] ci(temporal): cache test-server binary to avoid 429 rate limits The temporal_env fixture calls WorkflowEnvironment.start_time_skipping(), which downloads the Java test-server binary from temporal.download on every run. GitHub Actions runners regularly hit 429 there, flaking the temporal nox shards. Cache the binary across CI runs and pass it via test_server_existing_path. The fixture honors a new BRAINTRUST_TEMPORAL_TEST_SERVER_PATH env var; when unset (the local default), the SDK's normal download behavior is preserved. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/checks.yaml | 43 +++++++++++++++++++ .../integrations/temporal/test_temporal.py | 15 ++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index b2960cb8..e0d50d1d 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -87,12 +87,55 @@ jobs: os: [ubuntu-24.04, windows-2025] shard: [0, 1, 2, 3, 4, 5] + env: + # Pinned version of the temporal-test-server binary used by + # temporalio.testing.WorkflowEnvironment.start_time_skipping. Bumping + # this invalidates the cache and pulls a fresh binary on next CI run. + TEMPORAL_TEST_SERVER_VERSION: "1.35.0" + steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Python environment uses: ./.github/actions/setup-python-env with: python-version: ${{ matrix.python-version }} + - name: Cache Temporal test server binary + if: runner.os == 'Linux' + id: temporal-test-server-cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.cache/braintrust/temporal-test-server + key: temporal-test-server-${{ env.TEMPORAL_TEST_SERVER_VERSION }}-${{ runner.os }}-${{ runner.arch }} + - name: Download Temporal test server binary on cache miss + if: runner.os == 'Linux' && steps.temporal-test-server-cache.outputs.cache-hit != 'true' + shell: bash + run: | + set -euo pipefail + version="${TEMPORAL_TEST_SERVER_VERSION}" + arch="amd64" + [ "$(uname -m)" = "aarch64" ] && arch="arm64" + dir="$HOME/.cache/braintrust/temporal-test-server/${version}" + mkdir -p "$dir" + tarball="temporal-test-server_${version}_linux_${arch}.tar.gz" + url="https://temporal.download/assets/temporalio/sdk-java/releases/download/v${version}/${tarball}" + curl --fail --location --retry 5 --retry-all-errors --retry-delay 10 --max-time 180 -o "${dir}/${tarball}" "${url}" + tar -xzf "${dir}/${tarball}" -C "${dir}" + rm -f "${dir}/${tarball}" + - name: Configure Temporal test server path + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + version="${TEMPORAL_TEST_SERVER_VERSION}" + arch="amd64" + [ "$(uname -m)" = "aarch64" ] && arch="arm64" + binary="$HOME/.cache/braintrust/temporal-test-server/${version}/temporal-test-server_${version}_linux_${arch}/temporal-test-server" + if [ -f "$binary" ]; then + chmod +x "$binary" + echo "BRAINTRUST_TEMPORAL_TEST_SERVER_PATH=$binary" >> "$GITHUB_ENV" + else + echo "::warning::Cached temporal-test-server binary not found at $binary; tests will fall back to on-demand download" + fi - name: Run nox tests (shard ${{ matrix.shard }}/6) shell: bash run: | diff --git a/py/src/braintrust/integrations/temporal/test_temporal.py b/py/src/braintrust/integrations/temporal/test_temporal.py index 5a1a3fbc..a05911e5 100644 --- a/py/src/braintrust/integrations/temporal/test_temporal.py +++ b/py/src/braintrust/integrations/temporal/test_temporal.py @@ -1,6 +1,7 @@ """Unit tests for Braintrust Temporal interceptor.""" import asyncio +import os import uuid from dataclasses import dataclass from datetime import timedelta @@ -243,8 +244,18 @@ def test_contrib_temporal_compat_import_deprecated(self): @pytest_asyncio.fixture(scope="function") async def temporal_env(): - """Create a Temporal test environment.""" - async with await temporalio.testing.WorkflowEnvironment.start_time_skipping() as env: + """Create a Temporal test environment. + + If ``BRAINTRUST_TEMPORAL_TEST_SERVER_PATH`` is set and points at an existing file, + use it as the test server binary instead of downloading. CI sets this to a cached + binary to avoid hitting temporal.download rate limits; locally the var is unset + and the SDK falls back to its normal download behavior. + """ + kwargs: dict[str, Any] = {} + cached_path = os.environ.get("BRAINTRUST_TEMPORAL_TEST_SERVER_PATH") + if cached_path and os.path.isfile(cached_path): + kwargs["test_server_existing_path"] = cached_path + async with await temporalio.testing.WorkflowEnvironment.start_time_skipping(**kwargs) as env: yield env From a72949ad97a95f00045b120495ad2cc09a059c43 Mon Sep 17 00:00:00 2001 From: Starfolk Date: Fri, 1 May 2026 19:23:57 +0000 Subject: [PATCH 2/2] ci(temporal): switch caching strategy to download_dest_dir Replace the manually-pinned test-server version + curl/extract bash with the temporalio SDK's native download_dest_dir caching. The SDK keys cached binaries by Python SDK version (e.g. temporal-test-server-sdk-python-1.27.0) and reuses them on subsequent start_time_skipping calls, so CI just needs to persist the directory across runs and let each temporalio matrix entry populate the binary it wants. Cache key now hashes py/pyproject.toml so any matrix bump invalidates cleanly. Verified locally: cold cache setup ~1.4s, warm cache setup ~0.1s with HTTPS proxy black-holed (no re-download). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/checks.yaml | 47 +++++-------------- .../integrations/temporal/test_temporal.py | 18 ++++--- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index e0d50d1d..f0baf196 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -87,55 +87,32 @@ jobs: os: [ubuntu-24.04, windows-2025] shard: [0, 1, 2, 3, 4, 5] - env: - # Pinned version of the temporal-test-server binary used by - # temporalio.testing.WorkflowEnvironment.start_time_skipping. Bumping - # this invalidates the cache and pulls a fresh binary on next CI run. - TEMPORAL_TEST_SERVER_VERSION: "1.35.0" - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Python environment uses: ./.github/actions/setup-python-env with: python-version: ${{ matrix.python-version }} - - name: Cache Temporal test server binary + - name: Cache Temporal test server binaries if: runner.os == 'Linux' - id: temporal-test-server-cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: + # The temporalio Python SDK downloads its test-server binary into + # this directory (filename keyed by SDK version), and reuses any + # existing binary on subsequent calls. Caching the directory lets + # CI shards share binaries and avoid temporal.download 429s. path: ~/.cache/braintrust/temporal-test-server - key: temporal-test-server-${{ env.TEMPORAL_TEST_SERVER_VERSION }}-${{ runner.os }}-${{ runner.arch }} - - name: Download Temporal test server binary on cache miss - if: runner.os == 'Linux' && steps.temporal-test-server-cache.outputs.cache-hit != 'true' - shell: bash - run: | - set -euo pipefail - version="${TEMPORAL_TEST_SERVER_VERSION}" - arch="amd64" - [ "$(uname -m)" = "aarch64" ] && arch="arm64" - dir="$HOME/.cache/braintrust/temporal-test-server/${version}" - mkdir -p "$dir" - tarball="temporal-test-server_${version}_linux_${arch}.tar.gz" - url="https://temporal.download/assets/temporalio/sdk-java/releases/download/v${version}/${tarball}" - curl --fail --location --retry 5 --retry-all-errors --retry-delay 10 --max-time 180 -o "${dir}/${tarball}" "${url}" - tar -xzf "${dir}/${tarball}" -C "${dir}" - rm -f "${dir}/${tarball}" - - name: Configure Temporal test server path + key: temporal-test-server-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('py/pyproject.toml') }} + restore-keys: | + temporal-test-server-${{ runner.os }}-${{ runner.arch }}- + - name: Configure Temporal test server cache dir if: runner.os == 'Linux' shell: bash run: | set -euo pipefail - version="${TEMPORAL_TEST_SERVER_VERSION}" - arch="amd64" - [ "$(uname -m)" = "aarch64" ] && arch="arm64" - binary="$HOME/.cache/braintrust/temporal-test-server/${version}/temporal-test-server_${version}_linux_${arch}/temporal-test-server" - if [ -f "$binary" ]; then - chmod +x "$binary" - echo "BRAINTRUST_TEMPORAL_TEST_SERVER_PATH=$binary" >> "$GITHUB_ENV" - else - echo "::warning::Cached temporal-test-server binary not found at $binary; tests will fall back to on-demand download" - fi + dir="$HOME/.cache/braintrust/temporal-test-server" + mkdir -p "$dir" + echo "BRAINTRUST_TEMPORAL_TEST_SERVER_DIR=$dir" >> "$GITHUB_ENV" - name: Run nox tests (shard ${{ matrix.shard }}/6) shell: bash run: | diff --git a/py/src/braintrust/integrations/temporal/test_temporal.py b/py/src/braintrust/integrations/temporal/test_temporal.py index a05911e5..671ffd1e 100644 --- a/py/src/braintrust/integrations/temporal/test_temporal.py +++ b/py/src/braintrust/integrations/temporal/test_temporal.py @@ -246,15 +246,19 @@ def test_contrib_temporal_compat_import_deprecated(self): async def temporal_env(): """Create a Temporal test environment. - If ``BRAINTRUST_TEMPORAL_TEST_SERVER_PATH`` is set and points at an existing file, - use it as the test server binary instead of downloading. CI sets this to a cached - binary to avoid hitting temporal.download rate limits; locally the var is unset - and the SDK falls back to its normal download behavior. + If ``BRAINTRUST_TEMPORAL_TEST_SERVER_DIR`` is set, point the SDK's binary + download cache at that directory and pin a long TTL so existing binaries + are reused. CI sets this so a cached directory restored from the GitHub + Actions cache shortcuts the download to temporal.download (which rate + limits CI runners). When the var is unset (the local default), the SDK + falls back to its built-in temp-dir download behavior. """ kwargs: dict[str, Any] = {} - cached_path = os.environ.get("BRAINTRUST_TEMPORAL_TEST_SERVER_PATH") - if cached_path and os.path.isfile(cached_path): - kwargs["test_server_existing_path"] = cached_path + cache_dir = os.environ.get("BRAINTRUST_TEMPORAL_TEST_SERVER_DIR") + if cache_dir: + os.makedirs(cache_dir, exist_ok=True) + kwargs["download_dest_dir"] = cache_dir + kwargs["test_server_download_ttl"] = timedelta(days=365) async with await temporalio.testing.WorkflowEnvironment.start_time_skipping(**kwargs) as env: yield env