From 011b2fbde1cf53c6c5175097485c33114c9403e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 16:38:34 +0000 Subject: [PATCH 1/3] Configure workflow runs_on defaults for threading - Linting workflows (test-py-lint, test-ts-lint): singlethreaded - Testing workflows (test-*): multithreaded for parallel test execution - Other workflows: singlethreaded - Updated all workflows to use fromJSON(inputs.runs_on) for array support --- .github/workflows/build-next.yml | 4 ++-- .github/workflows/gh-submodules.yml | 4 ++-- .github/workflows/meta-regression-analysis.yml | 4 ++-- .github/workflows/publish-ghcr.yml | 4 ++-- .github/workflows/regression-test.yml | 4 ++-- .github/workflows/run-branch-test.yml | 8 ++++---- .github/workflows/test-bandit.yml | 8 ++++---- .github/workflows/test-cs-nunit.yml | 8 ++++---- .github/workflows/test-cs-xunit.yml | 8 ++++---- .github/workflows/test-js-jest.yml | 8 ++++---- .github/workflows/test-js-mocha.yml | 8 ++++---- .github/workflows/test-py-lint.yml | 2 +- .github/workflows/test-py-pytest.yml | 4 ++-- .github/workflows/test-py-unittest.yml | 8 ++++---- .github/workflows/test-storybook.yml | 8 ++++---- .github/workflows/test-ts-lint.yml | 8 ++++---- 16 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml index fba4693..0a7ee68 100644 --- a/.github/workflows/build-next.yml +++ b/.github/workflows/build-next.yml @@ -11,7 +11,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "singlethreaded"]' jobs: lint: uses: ./.github/workflows/test-ts-lint.yml @@ -21,7 +21,7 @@ jobs: lint_command: "npm run lint" fix_command: "npm run lint-fix" build-baremetal: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} name: Build / Baremetal needs: lint steps: diff --git a/.github/workflows/gh-submodules.yml b/.github/workflows/gh-submodules.yml index 4f779ab..e4b6576 100644 --- a/.github/workflows/gh-submodules.yml +++ b/.github/workflows/gh-submodules.yml @@ -15,11 +15,11 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "singlethreaded"]' jobs: manage-submodules: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} name: Manage Submodules permissions: contents: write diff --git a/.github/workflows/meta-regression-analysis.yml b/.github/workflows/meta-regression-analysis.yml index 46f7b5b..7fdbe9c 100644 --- a/.github/workflows/meta-regression-analysis.yml +++ b/.github/workflows/meta-regression-analysis.yml @@ -32,7 +32,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "singlethreaded"]' outputs: has_regressions: description: "Boolean indicating if regressions were found." @@ -46,7 +46,7 @@ on: jobs: analyze: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: HAS_REGRESSIONS: ${{ steps.check-regressions-script.outputs.HAS_REGRESSIONS }} REGRESSION_COUNT: ${{ steps.check-regressions-script.outputs.REGRESSION_COUNT }} diff --git a/.github/workflows/publish-ghcr.yml b/.github/workflows/publish-ghcr.yml index 5dd8136..e55ef1c 100644 --- a/.github/workflows/publish-ghcr.yml +++ b/.github/workflows/publish-ghcr.yml @@ -23,11 +23,11 @@ on: runs_on: required: false type: string - default: "ubuntu-latest" + default: '["ubuntu-latest"]' jobs: build-and-push-ghcr: name: Build and Push Docker Image to GHCR - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} permissions: contents: read # To checkout the repository packages: write # To push packages to GHCR diff --git a/.github/workflows/regression-test.yml b/.github/workflows/regression-test.yml index 108644b..73cfca6 100644 --- a/.github/workflows/regression-test.yml +++ b/.github/workflows/regression-test.yml @@ -7,7 +7,7 @@ on: description: "Runner label for the regression analysis job." required: false type: string - default: "ubuntu-latest" + default: '["ubuntu-latest"]' baseline_label: description: "Display name for the baseline (target) test results." required: true @@ -129,7 +129,7 @@ on: jobs: regression-analysis: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: has_regressions: ${{ steps.analyze.outputs.has_regressions }} regression_count: ${{ steps.analyze.outputs.regression_count }} diff --git a/.github/workflows/run-branch-test.yml b/.github/workflows/run-branch-test.yml index dc217bc..2d8a01f 100644 --- a/.github/workflows/run-branch-test.yml +++ b/.github/workflows/run-branch-test.yml @@ -16,7 +16,7 @@ on: description: "Runner label." required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' parallel_workers: description: "Number of parallel pytest workers. Use 'auto' for CPU count." required: false @@ -38,7 +38,7 @@ on: jobs: # Detect which test frameworks are present detect-frameworks: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: has_pytest: ${{ steps.detect.outputs.has_pytest }} # Future: has_jest, has_xunit, etc. @@ -74,7 +74,7 @@ jobs: test-target: needs: detect-frameworks if: needs.detect-frameworks.outputs.has_pytest == 'true' - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.results.outputs.total }} passed: ${{ steps.results.outputs.passed }} @@ -570,7 +570,7 @@ jobs: if: | always() && (needs.compare.outputs.has_regressions == 'true' || needs.compare.result == 'failure') - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} steps: - name: Send notification env: diff --git a/.github/workflows/test-bandit.yml b/.github/workflows/test-bandit.yml index e67951d..82237bb 100644 --- a/.github/workflows/test-bandit.yml +++ b/.github/workflows/test-bandit.yml @@ -11,7 +11,7 @@ on: runs_on: required: false type: string - default: "ubuntu-latest" + default: '["ubuntu-latest"]' outputs: bandit_issues_json: description: "JSON output of Bandit issues on PR branch" @@ -21,7 +21,7 @@ jobs: # Job 1: Run Bandit on the PR branch run-bandit: name: Run Bandit on PR Branch & Extract Results - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: bandit_issues_json: ${{ steps.extract-pr.outputs.BANDIT_JSON }} steps: @@ -63,7 +63,7 @@ jobs: # Job 2: Run Bandit on the target branch for comparison run-bandit-on-target: name: Run Bandit on Target Branch - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: bandit_target_json: ${{ steps.extract-target.outputs.TARGET_JSON }} steps: @@ -106,7 +106,7 @@ jobs: # Job 3: Compare the PR results against the target to detect regressions compare-bandit: name: Compare Bandit Issues (Regression Analysis) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} needs: [run-bandit, run-bandit-on-target] steps: - name: Compare JSON diff --git a/.github/workflows/test-cs-nunit.yml b/.github/workflows/test-cs-nunit.yml index 719baf9..042a7bd 100644 --- a/.github/workflows/test-cs-nunit.yml +++ b/.github/workflows/test-cs-nunit.yml @@ -30,7 +30,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL for failure notifications. If not provided, notifications are skipped." @@ -72,7 +72,7 @@ on: jobs: test-source-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -402,7 +402,7 @@ jobs: if-no-files-found: ignore test-target-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -773,7 +773,7 @@ jobs: needs.compare-results.result == 'failure' || needs.perform-regression-analysis.outputs.has_regressions == 'true' ) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: message_body: ${{ steps.construct_notification.outputs.message_body_out }} ping_user_ids: ${{ steps.construct_notification.outputs.ping_user_ids_out }} diff --git a/.github/workflows/test-cs-xunit.yml b/.github/workflows/test-cs-xunit.yml index e0eca3c..676c97d 100644 --- a/.github/workflows/test-cs-xunit.yml +++ b/.github/workflows/test-cs-xunit.yml @@ -30,7 +30,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL for failure notifications. If not provided, notifications are skipped." @@ -72,7 +72,7 @@ on: jobs: test-source-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -399,7 +399,7 @@ jobs: if-no-files-found: ignore test-target-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -770,7 +770,7 @@ jobs: needs.compare-results.result == 'failure' || needs.perform-regression-analysis.outputs.has_regressions == 'true' ) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: message_body: ${{ steps.construct_notification.outputs.message_body_out }} ping_user_ids: ${{ steps.construct_notification.outputs.ping_user_ids_out }} diff --git a/.github/workflows/test-js-jest.yml b/.github/workflows/test-js-jest.yml index 3c74c75..71f3f60 100644 --- a/.github/workflows/test-js-jest.yml +++ b/.github/workflows/test-js-jest.yml @@ -40,7 +40,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL for failure notifications. If not provided, notifications are skipped." @@ -82,7 +82,7 @@ on: jobs: test-source-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.extract-results.outputs.total }} passed: ${{ steps.extract-results.outputs.passed }} @@ -409,7 +409,7 @@ jobs: if-no-files-found: ignore test-target-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.check-collection.outputs.has_collection_errors == 'true' && steps.set-error-outputs.outputs.total || steps.extract-results.outputs.total }} passed: ${{ steps.check-collection.outputs.has_collection_errors == 'true' && steps.set-error-outputs.outputs.passed || steps.extract-results.outputs.passed }} @@ -806,7 +806,7 @@ jobs: needs.compare-results.result == 'failure' || needs.perform-regression-analysis.outputs.has_regressions == 'true' ) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: message_body: ${{ steps.construct_notification.outputs.message_body_out }} ping_user_ids: ${{ steps.construct_notification.outputs.ping_user_ids_out }} diff --git a/.github/workflows/test-js-mocha.yml b/.github/workflows/test-js-mocha.yml index 34f9c39..b47c52f 100644 --- a/.github/workflows/test-js-mocha.yml +++ b/.github/workflows/test-js-mocha.yml @@ -40,7 +40,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL for failure notifications. If not provided, notifications are skipped." @@ -82,7 +82,7 @@ on: jobs: test-source-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.extract-results.outputs.total }} passed: ${{ steps.extract-results.outputs.passed }} @@ -439,7 +439,7 @@ jobs: if-no-files-found: ignore test-target-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.check-collection.outputs.has_collection_errors == 'true' && steps.set-error-outputs.outputs.total || steps.extract-results.outputs.total }} passed: ${{ steps.check-collection.outputs.has_collection_errors == 'true' && steps.set-error-outputs.outputs.passed || steps.extract-results.outputs.passed }} @@ -869,7 +869,7 @@ jobs: needs.compare-results.result == 'failure' || needs.perform-regression-analysis.outputs.has_regressions == 'true' ) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: message_body: ${{ steps.construct_notification.outputs.message_body_out }} ping_user_ids: ${{ steps.construct_notification.outputs.ping_user_ids_out }} diff --git a/.github/workflows/test-py-lint.yml b/.github/workflows/test-py-lint.yml index 6248055..f0caf80 100644 --- a/.github/workflows/test-py-lint.yml +++ b/.github/workflows/test-py-lint.yml @@ -6,7 +6,7 @@ on: runs_on: required: false type: string - default: '["self-hosted", "multithreaded"]' + default: '["self-hosted", "singlethreaded"]' outputs: auto_formatted: description: "Whether Black auto-formatted files and pushed changes." diff --git a/.github/workflows/test-py-pytest.yml b/.github/workflows/test-py-pytest.yml index b366f33..284d7c1 100644 --- a/.github/workflows/test-py-pytest.yml +++ b/.github/workflows/test-py-pytest.yml @@ -17,7 +17,7 @@ on: description: "Runner label for the test job." required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' artifact_name: description: "Name for the test results artifact." required: true @@ -64,7 +64,7 @@ on: jobs: test: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.extract-results.outputs.total }} passed: ${{ steps.extract-results.outputs.passed }} diff --git a/.github/workflows/test-py-unittest.yml b/.github/workflows/test-py-unittest.yml index 6040e98..2c37a34 100644 --- a/.github/workflows/test-py-unittest.yml +++ b/.github/workflows/test-py-unittest.yml @@ -40,7 +40,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL for failure notifications. If not provided, notifications are skipped." @@ -82,7 +82,7 @@ on: jobs: test-source-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -642,7 +642,7 @@ PY if-no-files-found: ignore test-target-branch: - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} defaults: run: shell: bash @@ -1020,7 +1020,7 @@ PY needs.compare-results.result == 'failure' || needs.perform-regression-analysis.outputs.has_regressions == 'true' ) - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: message_body: ${{ steps.construct_notification.outputs.message_body_out }} ping_user_ids: ${{ steps.construct_notification.outputs.ping_user_ids_out }} diff --git a/.github/workflows/test-storybook.yml b/.github/workflows/test-storybook.yml index eca7415..44c280b 100644 --- a/.github/workflows/test-storybook.yml +++ b/.github/workflows/test-storybook.yml @@ -21,7 +21,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "multithreaded"]' outputs: pr_has_errors: description: "Boolean indicating if the PR branch has Storybook test errors." @@ -46,7 +46,7 @@ jobs: if: ${{ inputs.target_branch_to_compare != '' }} name: Test Target Branch Stories needs: [lint] # Ensure linting passes before running tests - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: total: ${{ steps.normalise-target.outputs.total }} passed: ${{ steps.normalise-target.outputs.passed }} @@ -126,7 +126,7 @@ jobs: test-pr-branch-storybook: name: Test PR Branch Stories needs: [lint] - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: has_errors: ${{ steps.run-tests-pr.outcome == 'failure' || steps.normalise-pr.outputs.has_failures == 'true' }} failing_stories_json: ${{ steps.normalise-pr.outputs.failing_items_json }} @@ -243,7 +243,7 @@ jobs: check-storybook-results: name: Check Storybook Results & Regressions - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} needs: [test-pr-branch-storybook, compare-results, perform-regression-analysis] if: always() # Always run to give a final status steps: diff --git a/.github/workflows/test-ts-lint.yml b/.github/workflows/test-ts-lint.yml index 9af248e..30e956a 100644 --- a/.github/workflows/test-ts-lint.yml +++ b/.github/workflows/test-ts-lint.yml @@ -29,7 +29,7 @@ on: runs_on: required: false type: string - default: "self-hosted" + default: '["self-hosted", "singlethreaded"]' target_branch_artifact_name: required: false type: string @@ -55,7 +55,7 @@ jobs: # Only run if a target branch is specified for comparison if: ${{ inputs.target_branch_to_compare != '' }} name: Lint Target Branch - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: clean_files_json: ${{ steps.extract-clean-files.outputs.CLEAN_FILES_JSON }} steps: @@ -136,7 +136,7 @@ jobs: lint-pr-branch: name: Lint PR Branch & Attempt Fixes - runs-on: ${{ inputs.runs_on }} + runs-on: ${{ fromJSON(inputs.runs_on) }} outputs: lint_errors_json: ${{ steps.extract-errors-pr.outputs.LINT_ERRORS_JSON }} has_lint_errors: ${{ steps.check-errors-pr.outputs.HAS_LINT_ERRORS }} @@ -301,7 +301,7 @@ jobs: # check-results: # name: Check Lint Results - # runs-on: ${{ inputs.runs_on }} + # runs-on: ${{ fromJSON(inputs.runs_on) }} # needs: [lint-pr-branch, analyze-regressions] # if: always() # steps: From 06fbaa05826d13fa16758b425a1331dfa868db71 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 16:59:37 +0000 Subject: [PATCH 2/3] Fix pytest-xdist worker count to prevent OOM on constrained runners Change parallel_workers default from 'auto' to '4'. The 'auto' setting detects host/hypervisor CPUs (e.g., 20) rather than container-allocated resources, causing OOM kills when running on singlethreaded runners with 1 CPU and limited RAM. --- .github/workflows/test-py-pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-py-pytest.yml b/.github/workflows/test-py-pytest.yml index 284d7c1..945a68d 100644 --- a/.github/workflows/test-py-pytest.yml +++ b/.github/workflows/test-py-pytest.yml @@ -23,10 +23,10 @@ on: required: true type: string parallel_workers: - description: "Number of parallel workers for pytest-xdist. Use 'auto' for CPU count, or a number. Set to '1' to disable." + description: "Number of parallel workers for pytest-xdist. Use 'auto' for CPU count (caution: detects host CPUs, not container), or a number. Set to '1' to disable." required: false type: string - default: "auto" + default: "4" outputs: total: description: "Total number of tests" From a7b5ba636a906faf78caced357d2204eb5b907e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 17:01:16 +0000 Subject: [PATCH 3/3] Auto-detect pytest workers based on runner type - Multithreaded runner: 6 workers (matches 8 CPU / 8GB RAM) - Singlethreaded runner: 1 worker (matches 1 CPU / 1GB RAM) Default is now empty string which triggers auto-detection from the runs_on input. Explicit values can still be passed to override. --- .github/workflows/run-branch-test.yml | 4 ++-- .github/workflows/test-py-pytest.yml | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run-branch-test.yml b/.github/workflows/run-branch-test.yml index 2d8a01f..a57bcc0 100644 --- a/.github/workflows/run-branch-test.yml +++ b/.github/workflows/run-branch-test.yml @@ -18,10 +18,10 @@ on: type: string default: '["self-hosted", "multithreaded"]' parallel_workers: - description: "Number of parallel pytest workers. Use 'auto' for CPU count." + description: "Number of parallel pytest workers. Leave empty for auto-detect (6 for multithreaded, 1 for singlethreaded). Use 'auto' for CPU count (caution: detects host CPUs)." required: false type: string - default: "auto" + default: "" secrets: DISCORD_WEBHOOK_URL: required: false diff --git a/.github/workflows/test-py-pytest.yml b/.github/workflows/test-py-pytest.yml index 945a68d..e5ec325 100644 --- a/.github/workflows/test-py-pytest.yml +++ b/.github/workflows/test-py-pytest.yml @@ -23,10 +23,10 @@ on: required: true type: string parallel_workers: - description: "Number of parallel workers for pytest-xdist. Use 'auto' for CPU count (caution: detects host CPUs, not container), or a number. Set to '1' to disable." + description: "Number of parallel workers for pytest-xdist. Leave empty for auto-detect based on runner (6 for multithreaded, 1 for singlethreaded). Use 'auto' for CPU count (caution: detects host CPUs, not container), or a number." required: false type: string - default: "4" + default: "" outputs: total: description: "Total number of tests" @@ -152,10 +152,21 @@ jobs: continue-on-error: true if: steps.check-collection.outputs.has_collection_errors != 'true' run: | - echo "Running tests with ${{ inputs.parallel_workers }} workers..." + # Determine worker count based on input or runner type + WORKERS="${{ inputs.parallel_workers }}" + if [ -z "$WORKERS" ]; then + # Auto-detect based on runner type + if echo '${{ inputs.runs_on }}' | grep -q "multithreaded"; then + WORKERS="6" + else + WORKERS="1" + fi + fi + echo "Running tests with $WORKERS workers..." + PARALLEL_FLAG="" - if [ "${{ inputs.parallel_workers }}" != "1" ]; then - PARALLEL_FLAG="-n ${{ inputs.parallel_workers }}" + if [ "$WORKERS" != "1" ]; then + PARALLEL_FLAG="-n $WORKERS" fi # Run pytest and capture exit code