From 0562eecd241c071fb0bd9fe3c52da98f828de34f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:50:33 +0000 Subject: [PATCH 1/3] Initial plan From e346e22944f0419532838776559a39e0b52dc118 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:56:28 +0000 Subject: [PATCH 2/3] Fix Release Stage 1 race condition by implementing sequential job execution - Replace parallel matrix strategy with sequential jobs - Add proper job dependencies (alpha -> beta -> stable) - Use always() conditions for error resilience - Maintain conditional execution based on release types - Add comprehensive test suite for validation Co-authored-by: NorthernMan54 <19808920+NorthernMan54@users.noreply.github.com> --- .../release-stage-1_update_dependencies.yml | 93 ++++++-- RACE_CONDITION_FIX.md | 46 ++++ test/test-race-condition-fix.sh | 199 ++++++++++++++++++ 3 files changed, 326 insertions(+), 12 deletions(-) create mode 100644 RACE_CONDITION_FIX.md create mode 100755 test/test-race-condition-fix.sh diff --git a/.github/workflows/release-stage-1_update_dependencies.yml b/.github/workflows/release-stage-1_update_dependencies.yml index 6db47c7..6083f06 100644 --- a/.github/workflows/release-stage-1_update_dependencies.yml +++ b/.github/workflows/release-stage-1_update_dependencies.yml @@ -41,37 +41,106 @@ jobs: echo 'matrix={"release_type":["alpha","beta","stable"],"include":[{"release_type":"alpha","config_file":".github/homebridge-alpha-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"beta","config_file":".github/homebridge-beta-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"stable","config_file":".github/homebridge-stable-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"}]}' >> $GITHUB_OUTPUT fi - update_dependencies: - name: Update ${{ matrix.release_type }} Dependencies + update_alpha_dependencies: + name: Update alpha Dependencies needs: determine-release-types - strategy: - matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} + if: contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'alpha') runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - - name: Run Homebridge ${{ matrix.release_type }} Bot + - name: Run Homebridge alpha Bot id: homebridge-bot uses: NorthernMan54/Homebridge-Dependency-Bot@latest with: - config_file: ${{ matrix.config_file }} - release_stream: ${{ matrix.release_type }} + config_file: .github/homebridge-alpha-bot.json + release_stream: alpha GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval - - name: Log Skipped ${{ matrix.release_type }} Stage 2 Trigger + - name: Log Skipped alpha Stage 2 Trigger if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' run: | - echo "::warning::${{ matrix.release_type }} Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" + echo "::warning::alpha Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" - - name: Trigger Build and Release ${{ matrix.release_type }} Package + - name: Trigger Build and Release alpha Package if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | - echo "::notice::Triggering ${{ matrix.release_type }} Stage 2 - Build and Release ${{ matrix.release_type }} Package" - gh workflow run ${{ matrix.trigger_workflow }} --ref latest --field release_type=${{ matrix.release_type }} || { echo "::error::Failed to trigger ${{ matrix.release_type }} Stage 2 workflow"; exit 1; } + echo "::notice::Triggering alpha Stage 2 - Build and Release alpha Package" + gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=alpha || { echo "::error::Failed to trigger alpha Stage 2 workflow"; exit 1; } + + - name: Checkout repository (for trigger step) + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + uses: actions/checkout@v4 + + update_beta_dependencies: + name: Update beta Dependencies + needs: [determine-release-types, update_alpha_dependencies] + if: always() && contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'beta') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Run Homebridge beta Bot + id: homebridge-bot + uses: NorthernMan54/Homebridge-Dependency-Bot@latest + with: + config_file: .github/homebridge-beta-bot.json + release_stream: beta + GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval + + - name: Log Skipped beta Stage 2 Trigger + if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' + run: | + echo "::warning::beta Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" + + - name: Trigger Build and Release beta Package + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + echo "::notice::Triggering beta Stage 2 - Build and Release beta Package" + gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=beta || { echo "::error::Failed to trigger beta Stage 2 workflow"; exit 1; } + + - name: Checkout repository (for trigger step) + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + uses: actions/checkout@v4 + + update_stable_dependencies: + name: Update stable Dependencies + needs: [determine-release-types, update_beta_dependencies] + if: always() && contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'stable') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Run Homebridge stable Bot + id: homebridge-bot + uses: NorthernMan54/Homebridge-Dependency-Bot@latest + with: + config_file: .github/homebridge-stable-bot.json + release_stream: stable + GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval + + - name: Log Skipped stable Stage 2 Trigger + if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' + run: | + echo "::warning::stable Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" + + - name: Trigger Build and Release stable Package + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + echo "::notice::Triggering stable Stage 2 - Build and Release stable Package" + gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=stable || { echo "::error::Failed to trigger stable Stage 2 workflow"; exit 1; } - name: Checkout repository (for trigger step) if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' diff --git a/RACE_CONDITION_FIX.md b/RACE_CONDITION_FIX.md new file mode 100644 index 0000000..2b682ae --- /dev/null +++ b/RACE_CONDITION_FIX.md @@ -0,0 +1,46 @@ +# Race Condition Fix Summary + +## Issue Description +Release Stage 1 workflow (run #17525831245) failed with the error: +``` +GraphQL: Base branch was modified. Review and try the merge again. (mergePullRequest) +``` + +## Root Cause +The workflow used a matrix strategy that ran alpha, beta, and stable dependency update jobs in parallel. When multiple jobs tried to create and merge PRs simultaneously, they created a race condition where one job would modify the base branch while another was trying to merge, causing the second job to fail. + +## Solution Implemented +Replaced the parallel matrix strategy with sequential job execution: + +1. **Removed Matrix Strategy**: Eliminated the `strategy.matrix` configuration that caused parallel execution +2. **Created Sequential Jobs**: Split into three separate jobs: + - `update_alpha_dependencies` (runs first) + - `update_beta_dependencies` (runs after alpha) + - `update_stable_dependencies` (runs after beta) +3. **Added Proper Dependencies**: Each job depends on the previous one to ensure sequential execution +4. **Used `always()` Conditions**: Beta and stable jobs use `always()` to continue even if previous jobs fail +5. **Maintained Conditional Execution**: Each job only runs if its release type is in the determined matrix + +## Technical Changes +- **File Modified**: `.github/workflows/release-stage-1_update_dependencies.yml` +- **Lines Changed**: Replaced matrix-based job (lines 44-78) with three sequential jobs (~80 lines) +- **Key Features**: + - Sequential execution prevents race conditions + - `always()` conditions provide error resilience + - Conditional execution based on release type matrix + - Hardcoded config file references for clarity + +## Testing +Created comprehensive test suite (`test/test-race-condition-fix.sh`) that validates: +- YAML syntax correctness +- Matrix strategy removal +- Separate job existence +- Proper job dependencies +- `always()` condition usage +- Conditional execution logic +- Config file references + +All tests pass, confirming the fix is properly implemented. + +## Expected Outcome +This fix should eliminate the race condition errors and allow Release Stage 1 to run successfully by ensuring only one bot operates on the repository at a time, preventing merge conflicts from simultaneous PR operations. \ No newline at end of file diff --git a/test/test-race-condition-fix.sh b/test/test-race-condition-fix.sh new file mode 100755 index 0000000..bb61386 --- /dev/null +++ b/test/test-race-condition-fix.sh @@ -0,0 +1,199 @@ +#!/bin/bash + +# Test script to validate the race condition fix for Release Stage 1 +# This script validates that the workflow no longer uses parallel matrix jobs + +set -e + +echo "๐Ÿงช Testing Race Condition Fix for Release Stage 1..." +echo + +# Test 1: Validate YAML syntax +echo "Test 1: Validating YAML syntax..." +if python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release-stage-1_update_dependencies.yml'))" 2>/dev/null; then + echo "โœ… YAML syntax is valid" +else + echo "โŒ YAML syntax error" + exit 1 +fi + +# Test 2: Check that matrix strategy is removed +echo +echo "Test 2: Checking matrix strategy removal..." +WORKFLOW_FILE=".github/workflows/release-stage-1_update_dependencies.yml" + +# Check that there's no matrix strategy in the workflow +if grep -q "strategy:" "$WORKFLOW_FILE"; then + echo "โŒ Matrix strategy still present in workflow" + exit 1 +else + echo "โœ… Matrix strategy successfully removed" +fi + +# Test 3: Check that separate jobs exist for each release type +echo +echo "Test 3: Checking separate jobs exist..." + +# Check for alpha job +if grep -q "update_alpha_dependencies:" "$WORKFLOW_FILE"; then + echo "โœ… Alpha dependencies job found" +else + echo "โŒ Alpha dependencies job missing" + exit 1 +fi + +# Check for beta job +if grep -q "update_beta_dependencies:" "$WORKFLOW_FILE"; then + echo "โœ… Beta dependencies job found" +else + echo "โŒ Beta dependencies job missing" + exit 1 +fi + +# Check for stable job +if grep -q "update_stable_dependencies:" "$WORKFLOW_FILE"; then + echo "โœ… Stable dependencies job found" +else + echo "โŒ Stable dependencies job missing" + exit 1 +fi + +# Test 4: Check job dependencies for sequential execution +echo +echo "Test 4: Checking job dependencies..." + +# Extract job dependencies using Python +ALPHA_NEEDS=$(python3 -c " +import yaml +with open('$WORKFLOW_FILE') as f: + w = yaml.safe_load(f) + needs = w['jobs']['update_alpha_dependencies'].get('needs', []) + if isinstance(needs, list): + print(','.join(needs)) + else: + print(needs) +" 2>/dev/null || echo "") + +BETA_NEEDS=$(python3 -c " +import yaml +with open('$WORKFLOW_FILE') as f: + w = yaml.safe_load(f) + needs = w['jobs']['update_beta_dependencies'].get('needs', []) + if isinstance(needs, list): + print(','.join(needs)) + else: + print(needs) +" 2>/dev/null || echo "") + +STABLE_NEEDS=$(python3 -c " +import yaml +with open('$WORKFLOW_FILE') as f: + w = yaml.safe_load(f) + needs = w['jobs']['update_stable_dependencies'].get('needs', []) + if isinstance(needs, list): + print(','.join(needs)) + else: + print(needs) +" 2>/dev/null || echo "") + +echo "Alpha job needs: $ALPHA_NEEDS" +echo "Beta job needs: $BETA_NEEDS" +echo "Stable job needs: $STABLE_NEEDS" + +# Alpha should only depend on determine-release-types +if [[ "$ALPHA_NEEDS" == "determine-release-types" ]]; then + echo "โœ… Alpha job has correct dependencies" +else + echo "โŒ Alpha job dependencies incorrect" + exit 1 +fi + +# Beta should depend on determine-release-types and update_alpha_dependencies +if [[ "$BETA_NEEDS" == *"determine-release-types"* && "$BETA_NEEDS" == *"update_alpha_dependencies"* ]]; then + echo "โœ… Beta job has correct dependencies" +else + echo "โŒ Beta job dependencies incorrect" + exit 1 +fi + +# Stable should depend on determine-release-types and update_beta_dependencies +if [[ "$STABLE_NEEDS" == *"determine-release-types"* && "$STABLE_NEEDS" == *"update_beta_dependencies"* ]]; then + echo "โœ… Stable job has correct dependencies" +else + echo "โŒ Stable job dependencies incorrect" + exit 1 +fi + +# Test 5: Check that always() conditions are used for proper error handling +echo +echo "Test 5: Checking always() conditions..." + +# Beta and stable jobs should use always() to continue even if previous jobs fail +if grep -A5 "update_beta_dependencies:" "$WORKFLOW_FILE" | grep -q "always()"; then + echo "โœ… Beta job uses always() condition" +else + echo "โŒ Beta job missing always() condition" + exit 1 +fi + +if grep -A5 "update_stable_dependencies:" "$WORKFLOW_FILE" | grep -q "always()"; then + echo "โœ… Stable job uses always() condition" +else + echo "โŒ Stable job missing always() condition" + exit 1 +fi + +# Test 6: Check that conditional execution based on matrix still works +echo +echo "Test 6: Checking conditional execution..." + +# All jobs should have conditions to check if they should run based on the determined matrix +for job in "alpha" "beta" "stable"; do + if grep -A5 "update_${job}_dependencies:" "$WORKFLOW_FILE" | grep -q "contains.*release_type.*${job}"; then + echo "โœ… ${job^} job has proper conditional execution" + else + echo "โŒ ${job^} job missing conditional execution" + exit 1 + fi +done + +# Test 7: Check that hardcoded config files are correct +echo +echo "Test 7: Checking config file references..." + +if grep -q ".github/homebridge-alpha-bot.json" "$WORKFLOW_FILE"; then + echo "โœ… Alpha config file correctly referenced" +else + echo "โŒ Alpha config file reference incorrect" + exit 1 +fi + +if grep -q ".github/homebridge-beta-bot.json" "$WORKFLOW_FILE"; then + echo "โœ… Beta config file correctly referenced" +else + echo "โŒ Beta config file reference incorrect" + exit 1 +fi + +if grep -q ".github/homebridge-stable-bot.json" "$WORKFLOW_FILE"; then + echo "โœ… Stable config file correctly referenced" +else + echo "โŒ Stable config file reference incorrect" + exit 1 +fi + +echo +echo "๐ŸŽ‰ All tests passed! The race condition fix is properly implemented." +echo +echo "Summary of changes:" +echo " - โœ… Removed parallel matrix strategy" +echo " - โœ… Created separate sequential jobs for alpha, beta, stable" +echo " - โœ… Added proper job dependencies to ensure sequential execution" +echo " - โœ… Used always() conditions for error resilience" +echo " - โœ… Maintained conditional execution based on release types" +echo " - โœ… Fixed hardcoded config file references" +echo +echo "This should resolve the race condition that caused:" +echo " - 'Base branch was modified. Review and try the merge again' errors" +echo " - Failed PR merges when multiple bots run simultaneously" +echo " - Release Stage 1 workflow failures" \ No newline at end of file From af346e905ce79898984c79c0c5dbaee79d12d3ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:10:14 +0000 Subject: [PATCH 3/3] Implement simpler race condition fix using max-parallel: 1 Replace sequential jobs approach with cleaner matrix strategy constraint: - Revert to original matrix structure - Add max-parallel: 1 to prevent race conditions - Update tests and documentation for new approach - Much simpler solution maintaining DRY principles Co-authored-by: NorthernMan54 <19808920+NorthernMan54@users.noreply.github.com> --- .../release-stage-1_update_dependencies.yml | 94 ++------- RACE_CONDITION_FIX.md | 90 ++++---- test/test-race-condition-fix.sh | 199 +++++++----------- 3 files changed, 141 insertions(+), 242 deletions(-) diff --git a/.github/workflows/release-stage-1_update_dependencies.yml b/.github/workflows/release-stage-1_update_dependencies.yml index 6083f06..89e7f95 100644 --- a/.github/workflows/release-stage-1_update_dependencies.yml +++ b/.github/workflows/release-stage-1_update_dependencies.yml @@ -41,106 +41,38 @@ jobs: echo 'matrix={"release_type":["alpha","beta","stable"],"include":[{"release_type":"alpha","config_file":".github/homebridge-alpha-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"beta","config_file":".github/homebridge-beta-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"stable","config_file":".github/homebridge-stable-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"}]}' >> $GITHUB_OUTPUT fi - update_alpha_dependencies: - name: Update alpha Dependencies + update_dependencies: + name: Update ${{ matrix.release_type }} Dependencies needs: determine-release-types - if: contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'alpha') + strategy: + max-parallel: 1 + matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - - name: Run Homebridge alpha Bot + - name: Run Homebridge ${{ matrix.release_type }} Bot id: homebridge-bot uses: NorthernMan54/Homebridge-Dependency-Bot@latest with: - config_file: .github/homebridge-alpha-bot.json - release_stream: alpha + config_file: ${{ matrix.config_file }} + release_stream: ${{ matrix.release_type }} GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval - - name: Log Skipped alpha Stage 2 Trigger + - name: Log Skipped ${{ matrix.release_type }} Stage 2 Trigger if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' run: | - echo "::warning::alpha Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" + echo "::warning::${{ matrix.release_type }} Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" - - name: Trigger Build and Release alpha Package + - name: Trigger Build and Release ${{ matrix.release_type }} Package if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | - echo "::notice::Triggering alpha Stage 2 - Build and Release alpha Package" - gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=alpha || { echo "::error::Failed to trigger alpha Stage 2 workflow"; exit 1; } - - - name: Checkout repository (for trigger step) - if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' - uses: actions/checkout@v4 - - update_beta_dependencies: - name: Update beta Dependencies - needs: [determine-release-types, update_alpha_dependencies] - if: always() && contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'beta') - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Run Homebridge beta Bot - id: homebridge-bot - uses: NorthernMan54/Homebridge-Dependency-Bot@latest - with: - config_file: .github/homebridge-beta-bot.json - release_stream: beta - GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval - - - name: Log Skipped beta Stage 2 Trigger - if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' - run: | - echo "::warning::beta Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" - - - name: Trigger Build and Release beta Package - if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - echo "::notice::Triggering beta Stage 2 - Build and Release beta Package" - gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=beta || { echo "::error::Failed to trigger beta Stage 2 workflow"; exit 1; } - - - name: Checkout repository (for trigger step) - if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' - uses: actions/checkout@v4 - - update_stable_dependencies: - name: Update stable Dependencies - needs: [determine-release-types, update_beta_dependencies] - if: always() && contains(fromJson(needs.determine-release-types.outputs.matrix).release_type, 'stable') - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Run Homebridge stable Bot - id: homebridge-bot - uses: NorthernMan54/Homebridge-Dependency-Bot@latest - with: - config_file: .github/homebridge-stable-bot.json - release_stream: stable - GH_TOKEN: ${{ secrets.GH_TOKEN }} # Separate token for PR approval - - - name: Log Skipped stable Stage 2 Trigger - if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' - run: | - echo "::warning::stable Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" - - - name: Trigger Build and Release stable Package - if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - echo "::notice::Triggering stable Stage 2 - Build and Release stable Package" - gh workflow run release-stage-2_build_and_release.yml --ref latest --field release_type=stable || { echo "::error::Failed to trigger stable Stage 2 workflow"; exit 1; } + echo "::notice::Triggering ${{ matrix.release_type }} Stage 2 - Build and Release ${{ matrix.release_type }} Package" + gh workflow run ${{ matrix.trigger_workflow }} --ref latest --field release_type=${{ matrix.release_type }} || { echo "::error::Failed to trigger ${{ matrix.release_type }} Stage 2 workflow"; exit 1; } - name: Checkout repository (for trigger step) if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' diff --git a/RACE_CONDITION_FIX.md b/RACE_CONDITION_FIX.md index 2b682ae..2749b29 100644 --- a/RACE_CONDITION_FIX.md +++ b/RACE_CONDITION_FIX.md @@ -1,46 +1,60 @@ -# Race Condition Fix Summary +# Race Condition Fix for Release Stage 1 Workflow -## Issue Description -Release Stage 1 workflow (run #17525831245) failed with the error: -``` -GraphQL: Base branch was modified. Review and try the merge again. (mergePullRequest) +This document explains the fix implemented for the race condition in the Release Stage 1 workflow that was causing "GraphQL: Base branch was modified. Review and try the merge again" errors. + +## Problem + +The Release Stage 1 workflow was failing with race condition errors when multiple dependency update bots (alpha, beta, stable) tried to merge PRs simultaneously: + +1. All three release types would run in parallel using the matrix strategy +2. Each would create PRs and attempt to auto-merge them at the same time +3. When one job merged its PR (modifying the base branch), other jobs would fail with "Base branch was modified" +4. This resulted in workflow failures like run #17525831245 + +## Solution + +The fix was implemented by adding a simple `max-parallel: 1` constraint to the existing matrix strategy: + +```yaml +strategy: + max-parallel: 1 + matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} ``` -## Root Cause -The workflow used a matrix strategy that ran alpha, beta, and stable dependency update jobs in parallel. When multiple jobs tried to create and merge PRs simultaneously, they created a race condition where one job would modify the base branch while another was trying to merge, causing the second job to fail. - -## Solution Implemented -Replaced the parallel matrix strategy with sequential job execution: - -1. **Removed Matrix Strategy**: Eliminated the `strategy.matrix` configuration that caused parallel execution -2. **Created Sequential Jobs**: Split into three separate jobs: - - `update_alpha_dependencies` (runs first) - - `update_beta_dependencies` (runs after alpha) - - `update_stable_dependencies` (runs after beta) -3. **Added Proper Dependencies**: Each job depends on the previous one to ensure sequential execution -4. **Used `always()` Conditions**: Beta and stable jobs use `always()` to continue even if previous jobs fail -5. **Maintained Conditional Execution**: Each job only runs if its release type is in the determined matrix - -## Technical Changes -- **File Modified**: `.github/workflows/release-stage-1_update_dependencies.yml` -- **Lines Changed**: Replaced matrix-based job (lines 44-78) with three sequential jobs (~80 lines) -- **Key Features**: - - Sequential execution prevents race conditions - - `always()` conditions provide error resilience - - Conditional execution based on release type matrix - - Hardcoded config file references for clarity +## Benefits + +This approach provides several advantages: + +- **โœ… Simple and minimal** - Only one line added to the existing workflow +- **โœ… Preserves matrix structure** - Maintains the clean matrix approach instead of duplicating jobs +- **โœ… Prevents race conditions** - Ensures only one bot operates at a time +- **โœ… No code duplication** - Keeps the DRY principle intact +- **โœ… Easy to understand** - Clear intent and implementation +- **โœ… Maintains existing logic** - All conditional execution and matrix variables preserved + +## How It Works + +The `max-parallel: 1` constraint ensures that: + +1. The matrix still defines all three jobs (alpha, beta, stable) +2. GitHub Actions will only run one matrix job at a time +3. Jobs execute sequentially, preventing simultaneous PR merge attempts +4. Race conditions are eliminated while preserving the clean matrix structure ## Testing -Created comprehensive test suite (`test/test-race-condition-fix.sh`) that validates: + +The fix includes a comprehensive test suite that validates: + - YAML syntax correctness -- Matrix strategy removal -- Separate job existence -- Proper job dependencies -- `always()` condition usage -- Conditional execution logic -- Config file references +- Matrix strategy with max-parallel constraint +- Single job structure (not separate sequential jobs) +- Matrix variable usage preservation +- Proper race condition prevention -All tests pass, confirming the fix is properly implemented. +Run tests with: + +```bash +./test/test-race-condition-fix.sh +``` -## Expected Outcome -This fix should eliminate the race condition errors and allow Release Stage 1 to run successfully by ensuring only one bot operates on the repository at a time, preventing merge conflicts from simultaneous PR operations. \ No newline at end of file +This fix resolves the race condition without the complexity of separate sequential jobs, making it both effective and maintainable. \ No newline at end of file diff --git a/test/test-race-condition-fix.sh b/test/test-race-condition-fix.sh index bb61386..8196a46 100755 --- a/test/test-race-condition-fix.sh +++ b/test/test-race-condition-fix.sh @@ -1,7 +1,7 @@ #!/bin/bash # Test script to validate the race condition fix for Release Stage 1 -# This script validates that the workflow no longer uses parallel matrix jobs +# This script validates that the workflow uses max-parallel: 1 to prevent race conditions set -e @@ -17,168 +17,118 @@ else exit 1 fi -# Test 2: Check that matrix strategy is removed +# Test 2: Check that matrix strategy still exists but with max-parallel constraint echo -echo "Test 2: Checking matrix strategy removal..." +echo "Test 2: Checking matrix strategy with max-parallel constraint..." WORKFLOW_FILE=".github/workflows/release-stage-1_update_dependencies.yml" -# Check that there's no matrix strategy in the workflow +# Check that matrix strategy is present if grep -q "strategy:" "$WORKFLOW_FILE"; then - echo "โŒ Matrix strategy still present in workflow" - exit 1 -else - echo "โœ… Matrix strategy successfully removed" -fi - -# Test 3: Check that separate jobs exist for each release type -echo -echo "Test 3: Checking separate jobs exist..." - -# Check for alpha job -if grep -q "update_alpha_dependencies:" "$WORKFLOW_FILE"; then - echo "โœ… Alpha dependencies job found" + echo "โœ… Matrix strategy found" else - echo "โŒ Alpha dependencies job missing" + echo "โŒ Matrix strategy missing" exit 1 fi -# Check for beta job -if grep -q "update_beta_dependencies:" "$WORKFLOW_FILE"; then - echo "โœ… Beta dependencies job found" +# Check that matrix configuration is present +if grep -q "matrix:" "$WORKFLOW_FILE"; then + echo "โœ… Matrix configuration found" else - echo "โŒ Beta dependencies job missing" + echo "โŒ Matrix configuration missing" exit 1 fi -# Check for stable job -if grep -q "update_stable_dependencies:" "$WORKFLOW_FILE"; then - echo "โœ… Stable dependencies job found" +# Test 3: Check that max-parallel: 1 constraint is present +echo +echo "Test 3: Checking max-parallel constraint..." + +if grep -q "max-parallel: 1" "$WORKFLOW_FILE"; then + echo "โœ… max-parallel: 1 constraint found" else - echo "โŒ Stable dependencies job missing" + echo "โŒ max-parallel: 1 constraint missing" exit 1 fi -# Test 4: Check job dependencies for sequential execution +# Test 4: Check that single update_dependencies job exists (not separate jobs) echo -echo "Test 4: Checking job dependencies..." +echo "Test 4: Checking single matrix job structure..." -# Extract job dependencies using Python -ALPHA_NEEDS=$(python3 -c " -import yaml -with open('$WORKFLOW_FILE') as f: - w = yaml.safe_load(f) - needs = w['jobs']['update_alpha_dependencies'].get('needs', []) - if isinstance(needs, list): - print(','.join(needs)) - else: - print(needs) -" 2>/dev/null || echo "") - -BETA_NEEDS=$(python3 -c " -import yaml -with open('$WORKFLOW_FILE') as f: - w = yaml.safe_load(f) - needs = w['jobs']['update_beta_dependencies'].get('needs', []) - if isinstance(needs, list): - print(','.join(needs)) - else: - print(needs) -" 2>/dev/null || echo "") - -STABLE_NEEDS=$(python3 -c " -import yaml -with open('$WORKFLOW_FILE') as f: - w = yaml.safe_load(f) - needs = w['jobs']['update_stable_dependencies'].get('needs', []) - if isinstance(needs, list): - print(','.join(needs)) - else: - print(needs) -" 2>/dev/null || echo "") - -echo "Alpha job needs: $ALPHA_NEEDS" -echo "Beta job needs: $BETA_NEEDS" -echo "Stable job needs: $STABLE_NEEDS" - -# Alpha should only depend on determine-release-types -if [[ "$ALPHA_NEEDS" == "determine-release-types" ]]; then - echo "โœ… Alpha job has correct dependencies" +if grep -q "update_dependencies:" "$WORKFLOW_FILE"; then + echo "โœ… Single update_dependencies job found" else - echo "โŒ Alpha job dependencies incorrect" + echo "โŒ update_dependencies job missing" exit 1 fi -# Beta should depend on determine-release-types and update_alpha_dependencies -if [[ "$BETA_NEEDS" == *"determine-release-types"* && "$BETA_NEEDS" == *"update_alpha_dependencies"* ]]; then - echo "โœ… Beta job has correct dependencies" -else - echo "โŒ Beta job dependencies incorrect" +# Ensure separate alpha/beta/stable jobs don't exist (those were from the old approach) +if grep -q "update_alpha_dependencies:" "$WORKFLOW_FILE"; then + echo "โŒ Separate alpha job found (should use matrix instead)" exit 1 +else + echo "โœ… No separate alpha job (using matrix approach correctly)" fi -# Stable should depend on determine-release-types and update_beta_dependencies -if [[ "$STABLE_NEEDS" == *"determine-release-types"* && "$STABLE_NEEDS" == *"update_beta_dependencies"* ]]; then - echo "โœ… Stable job has correct dependencies" +# Ensure separate beta/stable jobs don't exist (those were from the old approach) +if grep -q "update_beta_dependencies:" "$WORKFLOW_FILE"; then + echo "โŒ Separate beta job found (should use matrix instead)" + exit 1 else - echo "โŒ Stable job dependencies incorrect" + echo "โœ… No separate beta job (using matrix approach correctly)" +fi + +if grep -q "update_stable_dependencies:" "$WORKFLOW_FILE"; then + echo "โŒ Separate stable job found (should use matrix instead)" exit 1 +else + echo "โœ… No separate stable job (using matrix approach correctly)" fi -# Test 5: Check that always() conditions are used for proper error handling +# Test 5: Check that the job still uses matrix variables properly echo -echo "Test 5: Checking always() conditions..." +echo "Test 5: Checking matrix variable usage..." -# Beta and stable jobs should use always() to continue even if previous jobs fail -if grep -A5 "update_beta_dependencies:" "$WORKFLOW_FILE" | grep -q "always()"; then - echo "โœ… Beta job uses always() condition" +if grep -q "\${{ matrix.release_type }}" "$WORKFLOW_FILE"; then + echo "โœ… Matrix release_type variable found" else - echo "โŒ Beta job missing always() condition" + echo "โŒ Matrix release_type variable missing" exit 1 fi -if grep -A5 "update_stable_dependencies:" "$WORKFLOW_FILE" | grep -q "always()"; then - echo "โœ… Stable job uses always() condition" +if grep -q "\${{ matrix.config_file }}" "$WORKFLOW_FILE"; then + echo "โœ… Matrix config_file variable found" else - echo "โŒ Stable job missing always() condition" + echo "โŒ Matrix config_file variable missing" exit 1 fi -# Test 6: Check that conditional execution based on matrix still works -echo -echo "Test 6: Checking conditional execution..." - -# All jobs should have conditions to check if they should run based on the determined matrix -for job in "alpha" "beta" "stable"; do - if grep -A5 "update_${job}_dependencies:" "$WORKFLOW_FILE" | grep -q "contains.*release_type.*${job}"; then - echo "โœ… ${job^} job has proper conditional execution" - else - echo "โŒ ${job^} job missing conditional execution" - exit 1 - fi -done - -# Test 7: Check that hardcoded config files are correct +# Test 6: Check that the job name uses matrix variable echo -echo "Test 7: Checking config file references..." +echo "Test 6: Checking matrix job name..." -if grep -q ".github/homebridge-alpha-bot.json" "$WORKFLOW_FILE"; then - echo "โœ… Alpha config file correctly referenced" +if grep -q "Update \${{ matrix.release_type }} Dependencies" "$WORKFLOW_FILE"; then + echo "โœ… Job name uses matrix variable correctly" else - echo "โŒ Alpha config file reference incorrect" + echo "โŒ Job name does not use matrix variable" exit 1 fi -if grep -q ".github/homebridge-beta-bot.json" "$WORKFLOW_FILE"; then - echo "โœ… Beta config file correctly referenced" -else - echo "โŒ Beta config file reference incorrect" - exit 1 -fi +# Test 7: Verify that race condition is prevented +echo +echo "Test 7: Verifying race condition prevention..." -if grep -q ".github/homebridge-stable-bot.json" "$WORKFLOW_FILE"; then - echo "โœ… Stable config file correctly referenced" +# Check that max-parallel and matrix are in the same strategy block +STRATEGY_SECTION=$(python3 -c " +import yaml +with open('$WORKFLOW_FILE') as f: + w = yaml.safe_load(f) + strategy = w['jobs']['update_dependencies']['strategy'] + print('max-parallel' in strategy and 'matrix' in strategy) +" 2>/dev/null) + +if [[ "$STRATEGY_SECTION" == "True" ]]; then + echo "โœ… max-parallel and matrix are properly configured in strategy" else - echo "โŒ Stable config file reference incorrect" + echo "โŒ max-parallel and matrix not properly configured" exit 1 fi @@ -186,14 +136,17 @@ echo echo "๐ŸŽ‰ All tests passed! The race condition fix is properly implemented." echo echo "Summary of changes:" -echo " - โœ… Removed parallel matrix strategy" -echo " - โœ… Created separate sequential jobs for alpha, beta, stable" -echo " - โœ… Added proper job dependencies to ensure sequential execution" -echo " - โœ… Used always() conditions for error resilience" -echo " - โœ… Maintained conditional execution based on release types" -echo " - โœ… Fixed hardcoded config file references" +echo " - โœ… Kept the clean matrix strategy structure" +echo " - โœ… Added max-parallel: 1 constraint to prevent race conditions" +echo " - โœ… Maintained all existing matrix variable usage" +echo " - โœ… Preserved conditional execution logic" +echo " - โœ… Much simpler solution than sequential jobs" echo echo "This should resolve the race condition that caused:" echo " - 'Base branch was modified. Review and try the merge again' errors" echo " - Failed PR merges when multiple bots run simultaneously" -echo " - Release Stage 1 workflow failures" \ No newline at end of file +echo " - Release Stage 1 workflow failures" +echo +echo "The max-parallel: 1 constraint ensures that even though the matrix" +echo "defines multiple jobs (alpha, beta, stable), only one will run at a time," +echo "preventing the race condition while maintaining the clean matrix structure." \ No newline at end of file