From 2221bf2972f89a181b95bc50ec3167eb087922f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 07:11:37 +0200 Subject: [PATCH 1/9] ci(release): move releases to the persistent self-hosted runners novaloop-incus-large is the GARM ephemeral pool, whose runners were reaped mid-job; [self-hosted, Linux, X64] matches four persistent org runners. --- .../workflows/healthchat-release-playstore.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/healthchat-release-playstore.yml b/.github/workflows/healthchat-release-playstore.yml index d646ba9794..ba804f3972 100644 --- a/.github/workflows/healthchat-release-playstore.yml +++ b/.github/workflows/healthchat-release-playstore.yml @@ -46,17 +46,24 @@ concurrency: env: # Upstream's workflows hardcode -Xmx8g / Kotlin daemon -Xmx4g, sized for the - # 4-core/16GB ubuntu-latest that public repos get. On this container the - # daemon keeps getting SIGKILLed mid-build (DaemonDisappearedException, no - # OutOfMemoryError, no heap dump), so stay well under upstream's ceiling and - # leave room for the Kotlin daemon and the parallel lint workers. + # 4-core/16GB ubuntu-latest that public repos get. On the previous incus + # container the daemon kept getting SIGKILLed mid-build + # (DaemonDisappearedException, no OutOfMemoryError, no heap dump), so these + # values stay well under upstream's ceiling and leave room for the Kotlin + # daemon and the parallel lint workers. Re-check them against the "Report + # runner capacity" output now that jobs land on the persistent org runners, + # whose specs may differ. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: release: name: Build and upload AAB to Play Store - runs-on: novaloop-incus-large + # Persistent org runners (github-runner-01, gha-runner-1..3). The previous + # novaloop-incus-large label pointed at the GARM ephemeral pool, whose freshly + # spawned runners were reaped mid-job — jobs died ~10 minutes in with + # "The operation was canceled" and no other diagnostic. + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v4 with: From 9475ec3a484356f45599edadfcce039d7625f1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 07:11:37 +0200 Subject: [PATCH 2/9] ci: grant contents:read so checkout works on this private fork --- .github/workflows/build.yml | 9 ++++++++- .github/workflows/build_enterprise.yml | 6 +++++- .github/workflows/danger.yml | 6 +++++- .github/workflows/maestro-local.yml | 6 +++++- .github/workflows/quality.yml | 10 +++++++++- .github/workflows/recordScreenshots.yml | 6 +++++- .github/workflows/sonar.yml | 6 +++++- .github/workflows/tests.yml | 6 +++++- .github/workflows/validate-lfs.yml | 6 +++++- 9 files changed, 52 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99b5ce9ef0..af67604b3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,11 @@ on: push: branches: [ develop ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: @@ -19,6 +23,9 @@ jobs: name: Build APKs runs-on: ubuntu-latest permissions: + # A job-level block replaces the workflow-level one, so contents:read has to be + # repeated here or checkout fails on this private fork. + contents: read # For NejcZdovc/comment-pr pull-requests: write strategy: diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index c176864b46..4f447aafab 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -7,7 +7,11 @@ on: push: branches: [ develop ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index a8bf7b623a..ce1a792227 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -2,7 +2,11 @@ name: Danger CI on: [pull_request, merge_group] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read jobs: build: diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index 558d1b0a0c..f28e9951be 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -5,7 +5,11 @@ on: workflow_dispatch: pull_request: -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index b73a247347..22bfb72f61 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -7,7 +7,11 @@ on: push: branches: [ main, develop ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: @@ -331,6 +335,10 @@ jobs: name: Run zizmor runs-on: ubuntu-latest permissions: + # A job-level block replaces the workflow-level one, so contents:read has to be + # repeated here or checkout fails on this private fork. + contents: read + actions: read # Required for zizmor to read workflow files. security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 7465a6d6f8..811b613088 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -5,7 +5,11 @@ on: pull_request: types: [ labeled ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 4cb6bddf39..05600a524d 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -7,7 +7,11 @@ on: push: branches: [ main, develop ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5466bc80af..60032d50fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,11 @@ on: push: branches: [ main, develop ] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read # Enrich gradle.properties for CI/CD env: diff --git a/.github/workflows/validate-lfs.yml b/.github/workflows/validate-lfs.yml index 027c7d68e9..45c8fb8d29 100644 --- a/.github/workflows/validate-lfs.yml +++ b/.github/workflows/validate-lfs.yml @@ -2,7 +2,11 @@ name: Validate Git LFS on: [pull_request, merge_group] -permissions: {} +permissions: + # Private fork: actions/checkout needs contents:read to clone. Upstream is public, + # where an empty token can still read the repo, so an empty permissions block works there and + # fails here with `fatal: repository not found`. + contents: read jobs: build: From 0799ad5b73f2aebecb02a8c60579246ec5d60243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 08:07:51 +0200 Subject: [PATCH 3/9] ci: disable the Label Community PRs job on this fork --- .github/workflows/pull_request.yml | 59 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e24847280e..66c747a70c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -23,33 +23,38 @@ jobs: script: | core.setFailed("PR has been labeled with X-Blocked; it cannot be merged."); - community-prs: - name: Label Community PRs - runs-on: ubuntu-latest - if: github.event.action == 'opened' - permissions: - pull-requests: write - steps: - - name: Check membership - if: github.event.pull_request.user.login != 'renovate[bot]' - uses: tspascoal/get-user-teams-membership@b2546c5affc730fd8e3d8483ae9ad3621938c2f9 # v4.0.2 - id: teams - with: - username: ${{ github.event.pull_request.user.login }} - organization: element-hq - team: Vector Core - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN_READ_ORG }} - - name: Add label - if: steps.teams.outputs.isTeamMember == 'false' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['Z-Community-PR'] - }); + # Disabled on the HealthChat fork. This job asks whether the PR author belongs to the + # element-hq "Vector Core" team, using secrets.ELEMENT_BOT_TOKEN_READ_ORG — a secret + # that only exists upstream. Here it fails on every PR that is opened, and there is no + # community to label anyway. Kept commented rather than deleted so upstream changes to + # it still conflict visibly on merge. + # community-prs: + # name: Label Community PRs + # runs-on: ubuntu-latest + # if: github.event.action == 'opened' + # permissions: + # pull-requests: write + # steps: + # - name: Check membership + # if: github.event.pull_request.user.login != 'renovate[bot]' + # uses: tspascoal/get-user-teams-membership@b2546c5affc730fd8e3d8483ae9ad3621938c2f9 # v4.0.2 + # id: teams + # with: + # username: ${{ github.event.pull_request.user.login }} + # organization: element-hq + # team: Vector Core + # GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN_READ_ORG }} + # - name: Add label + # if: steps.teams.outputs.isTeamMember == 'false' + # uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + # with: + # script: | + # github.rest.issues.addLabels({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # labels: ['Z-Community-PR'] + # }); close-if-fork-develop: name: Forbid develop branch fork contributions From 0fcd7bcfcefb6626e96ac48a1b02e10d3c2513d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 08:42:36 +0200 Subject: [PATCH 4/9] ci: run the heavy Gradle jobs on the self-hosted runners Requires the Android SDK in the runner image. Also gates the disk-cleanup step to hosted runners, where it would otherwise strip the host. --- .github/workflows/build.yml | 5 ++++- .github/workflows/build_enterprise.yml | 5 ++++- .github/workflows/maestro-local.yml | 5 ++++- .github/workflows/quality.yml | 15 +++++++++------ .github/workflows/recordScreenshots.yml | 5 ++++- .github/workflows/sonar.yml | 5 ++++- .github/workflows/tests.yml | 5 ++++- 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af67604b3c..ece6d4b005 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ env: jobs: build: name: Build APKs - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] permissions: # A job-level block replaces the workflow-level one, so contents:read has to be # repeated here or checkout fails on this private fork. @@ -38,6 +38,9 @@ jobs: cancel-in-progress: true steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index 4f447aafab..3e22cee30e 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -21,7 +21,7 @@ env: jobs: build: name: Build Enterprise APKs - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Skip in forks if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} strategy: @@ -34,6 +34,9 @@ jobs: cancel-in-progress: true steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index f28e9951be..ec48715155 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -23,12 +23,15 @@ env: jobs: build-apk: name: Build APK - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] concurrency: group: ${{ format('maestro-build-{0}', github.ref) }} cancel-in-progress: true steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 22bfb72f61..d8ba6e252e 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -24,6 +24,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB @@ -67,7 +70,7 @@ jobs: checkDependencies: name: Search for invalid dependencies - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: @@ -91,7 +94,7 @@ jobs: # Code checks konsist: name: Konsist tests - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }} @@ -132,7 +135,7 @@ jobs: compose: name: Compose tests - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }} @@ -166,7 +169,7 @@ jobs: lint: name: Android lint check - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }} @@ -211,7 +214,7 @@ jobs: detekt: name: Detekt checks - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }} @@ -252,7 +255,7 @@ jobs: ktlint: name: Ktlint checks - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }} diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 811b613088..f1426fb279 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -23,11 +23,14 @@ jobs: pull-requests: write contents: write name: Record screenshots on branch ${{ github.event.pull_request.head.ref || github.ref_name }} - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'Record-Screenshots' steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 05600a524d..fc575ed5de 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -22,13 +22,16 @@ env: jobs: sonar: name: Sonar Quality Checks - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: group: ${{ format('sonar-{0}', github.ref) }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 60032d50fb..02d03880c6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ env: jobs: tests: name: Runs unit tests - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] # Allow all jobs on main and develop. Just one per PR. concurrency: @@ -29,6 +29,9 @@ jobs: cancel-in-progress: true steps: - name: Free Disk Space (Ubuntu) + # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this + # would delete dotnet/haskell/docker images from the host on every run. + if: runner.environment == 'github-hosted' uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be with: # This might remove tools that are actually needed, if set to "true" but frees about 6 GB From cedd070bfe59356cca4ac474da523be94ac4bff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 11:04:07 +0200 Subject: [PATCH 5/9] ci: rely on the runner image for the Android SDK Drops the setup-android step from the release job and gates the swapfile resize, which needs a hosted runner image and passwordless sudo. --- .../healthchat-release-playstore.yml | 20 ++++++++----------- .github/workflows/tests.yml | 3 +++ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/healthchat-release-playstore.yml b/.github/workflows/healthchat-release-playstore.yml index ba804f3972..734ff52ba9 100644 --- a/.github/workflows/healthchat-release-playstore.yml +++ b/.github/workflows/healthchat-release-playstore.yml @@ -183,18 +183,14 @@ jobs: distribution: 'temurin' java-version: '21' - # The self-hosted runner image has no Android SDK (unlike GitHub-hosted - # ubuntu-latest), so install it here. - # - platform: follows COMPILE_SDK in plugins/src/main/kotlin/Versions.kt. - # Platform packages are minor-versioned, so compileSdk 37 is android-37.0. - # - build-tools: nothing sets buildToolsVersion, so AGP picks its own - # default (SdkConstants.CURRENT_BUILD_TOOLS_VERSION). Bump it when AGP is - # upgraded, not when Versions.kt changes. - - name: Set up Android SDK - uses: android-actions/setup-android@v3 - with: - packages: 'platforms;android-37.0 build-tools;36.0.0' - + # The Android SDK now comes from the self-hosted runner image, so there is no + # setup-android step here. The image must provide it AND export ANDROID_HOME to + # the job environment — Gradle fails with "SDK location not found" otherwise. + # The image also has to carry the platform matching COMPILE_SDK in + # plugins/src/main/kotlin/Versions.kt (platform packages are minor-versioned, so + # compileSdk 37 means android-37.0) and the build-tools version AGP defaults to + # (SdkConstants.CURRENT_BUILD_TOOLS_VERSION), since nothing sets buildToolsVersion + # here. Both need refreshing in the image when AGP or COMPILE_SDK moves. - name: Configure gradle uses: gradle/actions/setup-gradle@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02d03880c6..fca7e8b63f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,9 @@ jobs: # Increase swapfile size to prevent screenshot tests getting terminated # https://github.com/actions/runner-images/discussions/7188#discussioncomment-6750749 - name: 💽 Increase swapfile size + # /mnt/swapfile is a GitHub runner-image path and this needs passwordless sudo, + # so it only applies to the hosted VMs. + if: runner.environment == 'github-hosted' run: | sudo swapoff -a sudo fallocate -l 8G /mnt/swapfile From 2013b86dfe6dda4c193564a133f76e9e6cd63df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 12:28:42 +0200 Subject: [PATCH 6/9] ci: size the Gradle heap for the self-hosted containers Upstream's -Xmx8g/4g is sized for hosted VMs; here the daemon is SIGKILLed and Gradle reports "the daemon has disappeared". --- .github/workflows/build.yml | 6 +++++- .github/workflows/build_enterprise.yml | 6 +++++- .github/workflows/maestro-local.yml | 6 +++++- .github/workflows/quality.yml | 6 +++++- .github/workflows/recordScreenshots.yml | 6 +++++- .github/workflows/sonar.yml | 6 +++++- .github/workflows/tests.yml | 6 +++++- 7 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ece6d4b005..6e0ee5bf68 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index 3e22cee30e..ce2f444cb9 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -15,7 +15,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index ec48715155..c15d12e617 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -13,7 +13,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache ARCH: x86_64 DEVICE: pixel_7_pro diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index d8ba6e252e..4a4eba2829 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -15,7 +15,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index f1426fb279..14e6e6cb39 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -13,7 +13,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g -Dsonar.gradle.skipCompile=true CI_GRADLE_ARG_PROPERTIES: --no-configuration-cache jobs: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index fc575ed5de..3c7d0cde8b 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -15,7 +15,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true --no-configuration-cache GROUP: ${{ format('sonar-{0}', github.ref) }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fca7e8b63f..7a3b006c9a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,11 @@ permissions: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC + # Sized for the self-hosted incus containers, matching + # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is + # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle + # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: From 409a1e5b9063df2f508664c3c611366a4fe6095e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 12:43:27 +0200 Subject: [PATCH 7/9] ci: report runner capacity on the self-hosted jobs --- .github/workflows/quality.yml | 9 +++++++++ .github/workflows/tests.yml | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 4a4eba2829..2d3af79c16 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -145,6 +145,15 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }} cancel-in-progress: true steps: + # Records the container's real limits so heap settings can be read against them + # instead of guessed at. Mirrors healthchat-release-playstore.yml. + - name: Report runner capacity + if: runner.environment == 'self-hosted' + run: | + free -h; nproc; df -h . + cat /sys/fs/cgroup/memory.max 2>/dev/null \ + || cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null \ + || echo "no cgroup memory limit visible" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # Ensure we are building the branch and not the branch after being merged on develop diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7a3b006c9a..d1eb35fca3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,15 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }} cancel-in-progress: true steps: + # Records the container's real limits so heap settings can be read against them + # instead of guessed at. Mirrors healthchat-release-playstore.yml. + - name: Report runner capacity + if: runner.environment == 'self-hosted' + run: | + free -h; nproc; df -h . + cat /sys/fs/cgroup/memory.max 2>/dev/null \ + || cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null \ + || echo "no cgroup memory limit visible" - name: Free Disk Space (Ubuntu) # Only meaningful on GitHub's throwaway VMs. On a persistent self-hosted runner this # would delete dotnet/haskell/docker images from the host on every run. From 8c4ef9264bbe19cfbadd9177c669d12e2ada855d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 12:55:48 +0200 Subject: [PATCH 8/9] ci: record why the Gradle heap is reduced --- .github/workflows/build.yml | 11 +++++++---- .github/workflows/build_enterprise.yml | 11 +++++++---- .github/workflows/maestro-local.yml | 11 +++++++---- .github/workflows/quality.yml | 11 +++++++---- .github/workflows/recordScreenshots.yml | 11 +++++++---- .github/workflows/sonar.yml | 11 +++++++---- .github/workflows/tests.yml | 11 +++++++---- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e0ee5bf68..0300faeab2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,10 +15,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index ce2f444cb9..ae34dd22aa 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -15,10 +15,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index c15d12e617..c7f93d2d12 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -13,10 +13,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache ARCH: x86_64 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 2d3af79c16..711b181c01 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -15,10 +15,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 14e6e6cb39..260fd9253e 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -13,10 +13,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g -Dsonar.gradle.skipCompile=true CI_GRADLE_ARG_PROPERTIES: --no-configuration-cache diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 3c7d0cde8b..d7d088587f 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -15,10 +15,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx2g CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true --no-configuration-cache GROUP: ${{ format('sonar-{0}', github.ref) }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d1eb35fca3..c156cd8f41 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,10 +15,13 @@ permissions: # Enrich gradle.properties for CI/CD env: - # Sized for the self-hosted incus containers, matching - # healthchat-release-playstore.yml. Upstream's -Xmx8g with a 4 GB Kotlin daemon is - # sized for GitHub's hosted VMs; here the daemon gets SIGKILLed mid-build and Gradle - # reports "the daemon has disappeared" with no OutOfMemoryError and no heap dump. + # Reduced from upstream's -Xmx8g / 4 GB Kotlin daemon for the self-hosted runners. + # Each incus container reports 48 GiB and 8 cores, so a single build has ample room — + # but the containers are oversubscribed against their host, and two concurrent heavy + # Gradle jobs get their daemons SIGKILLed ("the daemon has disappeared", no + # OutOfMemoryError, no heap dump). Running the same job alone succeeds. Lower heaps + # make two builds likelier to coexist; the real fix is sizing the containers to the + # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache From e74db93344b28a41520757359c82c05baf78e0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Tue, 4 Aug 2026 13:36:52 +0200 Subject: [PATCH 9/9] ci: cap Gradle workers so test JVMs fit the container --- .github/workflows/tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c156cd8f41..3dbea5c10a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,13 @@ env: # make two builds likelier to coexist; the real fix is sizing the containers to the # host. See the "Report runner capacity" step. GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache + # --max-workers caps how many test tasks run at once. build.gradle.kts sets + # maxParallelForks = cores/2 and screenshot tests fork at maxHeapSize=2g, so on these + # 8-core containers the default (workers = cores = 8) allows 8 x 4 x 2g = 64 GB of test + # JVMs against a 48 GiB cgroup limit, with no swap — the daemon gets OOM-killed and + # Gradle reports "the daemon has disappeared". GitHub's 4-core hosted runners reach only + # 2 x 2 x 2g = 16 GB and additionally have the 8 GB swapfile added above. + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache --max-workers=4 jobs: tests: