From f5fdd9331d7f78ccc3926b6b8b888eddf36615ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Thu, 16 Jul 2026 14:34:48 +0200 Subject: [PATCH] Make CI work on this internal fork (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make CI work on this internal fork Every check was red on every pull request. Upstream declares permissions: {} in its workflows, which costs nothing on a public repo and is fatal here: the token is limited to Metadata:read, so actions/checkout cannot read an internal repo and dies with a misleading "Repository not found". Repo settings cannot fix it — an explicit permissions block always overrides the default. Each job now gets the minimum it needs. Danger also reads the pull request through the issues API, so it needs issues:read alongside pull-requests:write, and zizmor's SARIF upload needs actions:read on a private repo. The unit tests move to the org's Mac Studio: GitHub-hosted macos-26 could not finish inside the hour and bills at 10x. Preview tests are skipped there, using the flag upstream already uses for its own enterprise tests — they render a snapshot per preview and never once finished on either runner. The trade is that preview snapshot regressions are no longer caught in CI. Workflows that only make sense upstream are gated off rather than deleted, so they keep rebasing cleanly. The enterprise tests need Element's bot token and their private submodule. Three nightlies fail at checkout every weeknight — this repo is a detached copy rather than a network fork, so GitHub never disabled their schedules — and one of them posts to Element's Codecov on every run. * Erase the test simulators before running the tests The unit tests assume a clean simulator, which costs nothing on a hosted VM because it hands out a fresh device every run. This runner reuses the same two devices, so app containers, caches and logs survive between runs and six suites fail on the previous run's leftovers rather than on anything in the code. LoggingTests is the clearest: it expects no log files and finds nine, in the same device container each time. Only the two devices the tooling pins are erased, resolved by UDID. A blanket erase would take out simulator state belonging to other work on a machine the release pipeline shares. * Remove the simulator reset again It was added on the theory that the runner's reused simulators were leaking state between runs. That theory was wrong: erasing both devices changed nothing — the same seven suites failed identically before and after. The leftover files LoggingTests trips on are its own, from a previous attempt of the same test within one run (the suite runs with -test-iterations 4), so nothing crosses runs for an erase to clean. Keeping a step that mutates shared state on the release machine for no measured benefit is worse than not having it. --- .github/workflows/accessibility-tests.yml | 9 ++- .github/workflows/compound-ios.yml | 21 +++--- .github/workflows/integration-tests.yml | 15 ++-- .github/workflows/ui-tests.yml | 14 ++-- .github/workflows/unit-tests-enterprise.yml | 9 ++- .github/workflows/unit-tests.yml | 77 +++++++++++++++++---- .github/workflows/zizmor.yml | 2 + 7 files changed, 116 insertions(+), 31 deletions(-) diff --git a/.github/workflows/accessibility-tests.yml b/.github/workflows/accessibility-tests.yml index 529ddfadd..fef1c8f1a 100644 --- a/.github/workflows/accessibility-tests.yml +++ b/.github/workflows/accessibility-tests.yml @@ -11,7 +11,12 @@ permissions: {} jobs: tests: name: Tests - runs-on: xcode-27 + # HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because + # permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather + # than deleted so the file keeps rebasing cleanly. Run locally with + # `swift run tools ci accessibility-tests`. + if: github.repository == 'element-hq/element-x-ios' + runs-on: macos-26 timeout-minutes: 150 concurrency: @@ -31,7 +36,7 @@ jobs: run: swift run -q tools ci accessibility-tests - name: Archive artifacts - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: Results diff --git a/.github/workflows/compound-ios.yml b/.github/workflows/compound-ios.yml index 35abf3a4d..7e8d19bd9 100644 --- a/.github/workflows/compound-ios.yml +++ b/.github/workflows/compound-ios.yml @@ -20,8 +20,13 @@ permissions: {} jobs: tests: - runs-on: xcode-27 - timeout-minutes: 25 + # HealthChat: skip in this fork. It only triggers on changes under compound-ios/, which this fork + # has never edited, so it would only ever fire on an upstream sync and go red at checkout — + # permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather + # than deleted so the file keeps rebasing cleanly. + if: github.repository == 'element-hq/element-x-ios' + runs-on: macos-26 + timeout-minutes: 15 concurrency: # When running on develop, use the sha to allow all runs of this workflow to run concurrently. @@ -34,8 +39,8 @@ jobs: with: persist-credentials: false - - name: Configure Xcode 27 - run: sudo xcode-select -s /Applications/Xcode_27.app + - name: Configure Xcode 26 + run: sudo xcode-select -s /Applications/Xcode_26.5.app - name: Install sourcery run: brew install sourcery @@ -45,11 +50,11 @@ jobs: run: sourcery --config Tools/Sourcery/PreviewTestsConfig.yml - name: Create simulator - run: xcrun simctl create "Snapshots" "iPhone SE (3rd generation)" "com.apple.CoreSimulator.SimRuntime.iOS-27-0" + run: xcrun simctl create "Snapshots" "iPhone SE (3rd generation)" "com.apple.CoreSimulator.SimRuntime.iOS-26-5" - name: Run tests working-directory: compound-ios - run: set -o pipefail && xcodebuild test -scheme 'Compound' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=Snapshots,OS=27.0' -skipPackagePluginValidation -resultBundlePath Logs/CompoundTests.xcresult | xcbeautify --renderer github-actions + run: set -o pipefail && xcodebuild test -scheme 'Compound' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=Snapshots,OS=26.5' -skipPackagePluginValidation -resultBundlePath Logs/CompoundTests.xcresult | xcbeautify --renderer github-actions - name: Zip artifacts if: failure() # We only care about artefacts if the tests fail @@ -57,7 +62,7 @@ jobs: run: zip -r Logs/CompoundTests.xcresult.zip Logs/CompoundTests.xcresult/ - name: Archive artifacts - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() # We only care about artefacts if the tests fail with: name: test-results @@ -66,7 +71,7 @@ jobs: if-no-files-found: ignore - name: Upload coverage to Codecov - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0fe579d3a..ac563cc90 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -11,7 +11,14 @@ permissions: {} jobs: integration_tests: name: Integration Tests - runs-on: xcode-27 + # HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, and it is the one + # job that still calls out to Element's Codecov — its upload guard is true on schedule events, so + # it POSTs slug/branch/SHA every night with continue-on-error hiding the failure. Fixing it would + # need a real healthchat.ch test account and three new secrets, and would still not work: + # HealthChatAppSettingsHook sets allowOtherAccountProviders: false, hiding the change-server UI + # these tests drive. Gated rather than deleted so the file keeps rebasing cleanly. + if: github.repository == 'element-hq/element-x-ios' + runs-on: macos-26 timeout-minutes: 45 concurrency: @@ -35,7 +42,7 @@ jobs: INTEGRATION_TESTS_PASSWORD: ${{ secrets.INTEGRATION_TESTS_PASSWORD }} - name: Archive artifacts - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # We only care about artefacts if the tests fail if: failure() with: @@ -49,7 +56,7 @@ jobs: - name: Upload coverage to Codecov # Skip if not successful and in forks if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: report_type: coverage files: test_output/integration-cobertura.xml @@ -61,7 +68,7 @@ jobs: - name: Upload test results to Codecov # Skip if cancelled and in forks if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 continue-on-error: true with: report_type: test_results diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index f9b8d01a5..76fcac3a7 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -15,7 +15,13 @@ permissions: {} jobs: tests: name: Tests - runs-on: xcode-27 + # HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because + # permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Fixing the + # permission would not help: the rebrand changed the start screen without re-recording any + # UITests snapshot, so it would fail on content instead. Gated rather than deleted so the file + # keeps rebasing cleanly. Run locally with `swift run tools ci ui-tests`. + if: github.repository == 'element-hq/element-x-ios' + runs-on: macos-26 timeout-minutes: 90 strategy: fail-fast: false @@ -46,7 +52,7 @@ jobs: GITHUB_EVENT_INPUTS_TEST_NAME: ${{ github.event.inputs.test_name }} - name: Archive artifacts - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # We only care about artefacts if the tests fail if: failure() with: @@ -58,7 +64,7 @@ jobs: - name: Upload coverage to Codecov # Skip if not successful and in forks if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: report_type: coverage files: test_output/ui-cobertura.xml @@ -70,7 +76,7 @@ jobs: - name: Upload test results to Codecov # Skip if cancelled and in forks if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 continue-on-error: true with: report_type: test_results diff --git a/.github/workflows/unit-tests-enterprise.yml b/.github/workflows/unit-tests-enterprise.yml index fd97fe0c4..c881c8fb5 100644 --- a/.github/workflows/unit-tests-enterprise.yml +++ b/.github/workflows/unit-tests-enterprise.yml @@ -17,7 +17,14 @@ jobs: timeout-minutes: 60 # Skip in forks - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + # HealthChat: this job can never pass in this fork — it needs + # secrets.ELEMENT_BOT_TOKEN (Element's bot, absent here) and recursively + # checks out the private element-hq/element-ios-enterprise submodule, which + # Novaloop cannot obtain. The guard below only asks whether the PR comes + # FROM a fork, not whether this repo IS one, so it does not skip us. Gate on + # the upstream repo as post-release.yml does. Gated rather than deleted so + # the file keeps rebasing cleanly against upstream. + if: ${{ github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} concurrency: # Only allow a single run of this workflow on each branch, automatically cancelling older runs. diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 6685c042b..85f4ab9f3 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -13,8 +13,18 @@ permissions: {} jobs: tests: name: Tests - runs-on: xcode-27 - timeout-minutes: 80 + # HealthChat: GitHub-hosted macos-26 could not finish this job inside + # timeout-minutes: 60 (two attempts: 51m42s, then killed at 60m44s), and macOS + # minutes bill at 10x. novaloop-mac-studio-001 is the org's Mac Studio, already + # used by the release pipeline; its labels are [self-hosted, macOS, ARM64]. + runs-on: [self-hosted, macOS] + # Upstream's 60 is kept: with previews skipped (see Run tests) the build plus unit tests measured + # ~16 min here. A run approaching this budget is a signal, not normal — the earlier 60m timeouts + # were preview tests, not the build. + timeout-minutes: 60 + + permissions: + contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout (and on git lfs pull). Upstream is public, hence permissions: {} there. concurrency: # Only allow a single run of this workflow on each branch, automatically cancelling older runs. @@ -22,12 +32,44 @@ jobs: cancel-in-progress: true steps: + # HealthChat: upstream passes persist-credentials: false here, which the + # composite forwards to actions/checkout — stripping the auth header before + # its separate "Git LFS Pull" step runs. Upstream can afford that because + # its repo is public and git lfs pull works anonymously; this fork is + # INTERNAL, so the LFS batch endpoint would 404. Keep the composite's own + # default (true) so the pull is authenticated with GITHUB_TOKEN. - uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5 - with: - persist-credentials: false - - name: Setup environment - run: source ci_scripts/ci_common.sh && setup_github_actions_environment + # HealthChat: verify only — never provision. Upstream's + # setup_github_actions_environment cannot run on this runner: it calls + # `sudo xcode-select -s /Applications/Xcode_26.5.0.app`, but the runner has no + # passwordless sudo and no such path (only /Applications/Xcode.app exists), and + # it calls `brew update && brew install ...` — novaloop-mac-studio-001 is the + # shared release machine, so a per-run `brew update` could upgrade xcodegen + # underneath the release pipeline. These tools are installed once by hand; CI + # only asserts they are present and fails loudly, naming the fix, if not. + - name: Verify environment + run: | + set -eu + missing="" + formulae="" + for tool in xcodegen swiftlint swiftformat xcbeautify xcresultparser pkl git-lfs; do + if ! command -v "$tool" >/dev/null 2>&1; then + missing="$missing $tool" + case "$tool" in + # Not in homebrew-core; only this tap ships it. + xcresultparser) formulae="$formulae a7ex/homebrew-formulae/xcresultparser" ;; + *) formulae="$formulae $tool" ;; + esac + fi + done + if [ -n "$missing" ]; then + echo "::error::Missing on self-hosted runner:${missing}. Install by hand on novaloop-mac-studio-001, then re-run: brew install${formulae}" + exit 1 + fi + # The Xcode version is no longer pinned anywhere, so this is the only record + # of which Xcode built a given run. + xcodebuild -version - name: Restore compilation cache uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -37,7 +79,12 @@ jobs: restore-keys: compilation-cache-unit-tests- - name: Run tests - run: swift run -q tools ci unit-tests + # HealthChat: previews are skipped. PreviewTests renders a snapshot for every preview in the + # app and was the entire reason this job could not fit in an hour — it never once finished, + # on either runner. Unit tests alone take ~16 min here. Upstream skips them the same way in + # unit-tests-enterprise.yml. Cost: preview snapshot regressions are not caught in CI, so + # re-record and check them locally when changing UI. + run: swift run -q tools ci unit-tests --skip-previews - name: Save compilation cache # Only develop populates the cache, all of the other runs simply read from it. @@ -48,7 +95,7 @@ jobs: key: compilation-cache-unit-tests-${{ github.sha }} - name: Archive artifacts - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # We only care about artefacts if the tests fail if: failure() with: @@ -59,8 +106,12 @@ jobs: - name: Upload coverage to Codecov # Skip if not successful and in forks - if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + # HealthChat: the existing guard only asks whether the PR comes FROM a + # fork, not whether this repo IS one, so it is true here and the step + # runs without a CODECOV_TOKEN — which fails the job via + # fail_ci_if_error. Gate on the upstream repo as post-release.yml does. + if: ${{ success() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: report_type: coverage files: test_output/unit-cobertura.xml,test_output/preview-cobertura.xml @@ -71,8 +122,10 @@ jobs: - name: Upload test results to Codecov # Skip if cancelled and in forks - if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + # HealthChat: gated on the upstream repo for the same reason as the + # coverage step above — no CODECOV_TOKEN exists in this fork. + if: ${{ !cancelled() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 continue-on-error: true with: report_type: test_results diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 308554586..253127675 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest permissions: security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. + contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout. Upstream is public, hence permissions: {} there. + actions: read # HealthChat: upload-sarif additionally needs this on private/internal repos; public upstream does not. steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1