Skip to content
Merged
158 changes: 158 additions & 0 deletions .cursor/rules/github-workflow.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,161 @@ For reusable workflows (workflow_call), use descriptive names that indicate thei
- Ensure names remain meaningful when viewed in GitHub's status check UI
- Test names in the GitHub PR interface before committing changes
- For lint workflows, use simple "Lint" job name since the tool is already specified in the workflow name

---

## GitHub Actions Concurrency Strategy

### Overview

This document outlines the concurrency configuration strategy for all GitHub Actions workflows in the Sentry Cocoa repository. The strategy optimizes CI resource usage while ensuring critical runs (like main branch pushes) are never interrupted.

### Core Principles

#### 1. Resource Optimization
- **Cancel outdated PR runs** - When new commits are pushed to a PR, cancel the previous workflow run since only the latest commit matters for merge decisions
- **Protect critical runs** - Never cancel workflows running on main branch, release branches, or scheduled runs as these are essential for maintaining baseline quality and release integrity
- **Per-branch grouping** - Use `github.ref` for consistent concurrency grouping across all branch types

#### 2. Consistent Patterns
All workflows follow standardized concurrency patterns based on their trigger types and criticality.

### Concurrency Patterns

#### Pattern 1: Conditional Cancellation (Most Common)
**Used by:** Most workflows that run on both main/release branches AND pull requests

```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
```

**Behavior:**
- ✅ Cancels in-progress runs when new commits are pushed to PRs
- ✅ Never cancels runs on main branch pushes
- ✅ Never cancels runs on release branch pushes
- ✅ Never cancels scheduled runs
- ✅ Never cancels manual workflow_dispatch runs

**Examples:** `test.yml`, `build.yml`, `benchmarking.yml`, `ui-tests.yml`, all lint workflows

#### Pattern 2: Always Cancel (PR-Only Workflows)
**Used by:** Workflows that ONLY run on pull requests

```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
```

**Behavior:**
- ✅ Always cancels in-progress runs (safe since they only run on PRs)
- ✅ Provides immediate feedback on latest changes

**Examples:** `danger.yml`, `api-stability.yml`, `changes-in-high-risk-code.yml`

#### Pattern 3: Fixed Group Name (Special Cases)
**Used by:** Utility workflows with specific requirements

```yaml
concurrency:
group: "auto-update-tools"
cancel-in-progress: true
```

**Example:** `auto-update-tools.yml` (uses fixed group name for global coordination)

### Implementation Details

#### Group Naming Convention
- **Standard:** `${{ github.workflow }}-${{ github.ref }}`
- **Benefits:**
- Unique per workflow and branch/PR
- Consistent across all workflow types
- Works with main, release, and feature branches
- Handles PRs and direct pushes uniformly

#### Why `github.ref` Instead of `github.head_ref || github.run_id`?
- **Simpler logic** - No conditional expressions needed
- **Consistent behavior** - Same pattern works for all trigger types
- **Per-branch grouping** - Natural grouping by branch without special cases
- **Better maintainability** - Single pattern to understand and maintain

#### Cancellation Logic Evolution
**Before:**
```yaml
cancel-in-progress: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/main') && github.event_name != 'schedule' }}
```

**After:**
```yaml
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
```

**Why simplified:**
- ✅ Much more readable and maintainable
- ✅ Functionally identical behavior
- ✅ Clear intent: "only cancel on pull requests"
- ✅ Less prone to errors

### Workflow-Specific Configurations

#### High-Resource Workflows
**Examples:** `benchmarking.yml`, `ui-tests.yml`
- Use conditional cancellation to protect expensive main branch runs
- Include detailed comments explaining resource considerations
- May include special cleanup steps (e.g., SauceLabs job cancellation)

#### Fast Validation Workflows
**Examples:** All lint workflows, `danger.yml`
- Use appropriate cancellation strategy based on trigger scope
- Focus on providing quick feedback on latest changes

#### Critical Infrastructure Workflows
**Examples:** `test.yml`, `build.yml`, `release.yml`
- Never cancel on main/release branches to maintain quality gates
- Ensure complete validation of production-bound code

### Documentation Requirements

Each workflow's concurrency block must include comments explaining:

1. **Purpose** - Why concurrency control is needed for this workflow
2. **Resource considerations** - Any expensive operations (SauceLabs, device time, etc.)
3. **Branch protection logic** - Why main/release branches need complete runs
4. **User experience** - How the configuration improves feedback timing

#### Example Documentation:
```yaml
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple benchmark runs on the same code,
# as benchmarks are extremely resource-intensive and require dedicated device time on SauceLabs.
# - For pull requests, we cancel in-progress runs when new commits are pushed to avoid wasting
# expensive external testing resources and provide timely performance feedback.
# - For main branch pushes, we never cancel benchmarks to ensure we have complete performance
# baselines for every main branch commit, which are critical for performance regression detection.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
```

### Maintenance Guidelines

#### When Adding New Workflows
1. **Identify trigger scope** - Does it run on main/release branches?
2. **Choose appropriate pattern** - Conditional vs always cancel
3. **Add documentation** - Explain the resource and timing considerations
4. **Follow naming convention** - Use standard group naming pattern

#### When Modifying Existing Workflows
1. **Preserve protection** - Don't break main/release branch safeguards
2. **Update documentation** - Keep comments accurate and helpful
3. **Test edge cases** - Verify behavior with scheduled/manual triggers
4. **Consider resource impact** - Evaluate cost of additional runs

#### Red Flags to Avoid
- ❌ Never use `cancel-in-progress: true` on workflows that run on main/release branches
- ❌ Don't create complex conditional logic when simple patterns work
- ❌ Avoid custom group names unless absolutely necessary
- ❌ Don't skip documentation - future maintainers need context
9 changes: 9 additions & 0 deletions .github/workflows/api-stability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ on:
- "sdk_api.json"
- "sdk_api_v9.json"

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple API stability checks on the same code,
# as these analyze public API changes and generate detailed breaking change reports.
# - We always cancel in-progress runs since this workflow only runs on pull requests, and only
# the latest commit's API changes matter for determining if the PR introduces breaking changes.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
api-stability:
name: Check API Stability (${{ matrix.version }})
Expand Down
17 changes: 14 additions & 3 deletions .github/workflows/benchmarking.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ on:
- ".github/workflows/build-xcframework-variant-slices.yml"
- ".github/workflows/assemble-xcframework-variant.yml"

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple benchmark runs on the same code,
# as benchmarks are extremely resource-intensive and require dedicated device time on SauceLabs.
# - For pull requests, we cancel in-progress runs when new commits are pushed to avoid wasting
# expensive external testing resources and provide timely performance feedback.
# - For main branch pushes, we never cancel benchmarks to ensure we have complete performance
# baselines for every main branch commit, which are critical for performance regression detection.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build-benchmark-test-target:
Expand Down Expand Up @@ -129,6 +135,11 @@ jobs:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
with:
# Retry Logic: This step only runs when the benchmark test has failed.
# We determine if the failure was due to SauceLabs infrastructure issues
# (which can be fixed by retrying) or legitimate test failures (which cannot).
# SauceLabs internal errors frequently cause CI failures that can be resolved
# by re-running the test. See scripts/saucelabs-helpers.js for detailed logic.
script: |
const fs = require('fs');
const { execSync } = require('child_process');
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ on:
- "Makefile" # Make commands used for CI build setup
- "Brewfile*" # Dependency installation affects build environment

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple build runs of the same code,
# which would waste CI resources without providing additional value.
# - For pull requests, we cancel in-progress builds when new commits are pushed since only the
# latest commit's build results matter for merge decisions.
# - For main branch pushes, we never cancel builds to ensure all release and sample builds complete,
# as broken builds on main could block releases and affect downstream consumers.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# We had issues that the release build was broken on main.
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/changes-in-high-risk-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ name: Changes In High Risk Code
on:
pull_request:

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple high-risk code analysis runs,
# as these detect changes to critical code paths and trigger additional validation workflows.
# - We always cancel in-progress runs since this workflow only runs on pull requests, and only
# the latest commit's high-risk code changes matter for determining which additional tests to run.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ on:
schedule:
- cron: "40 4 * * 6" # Weekly scheduled run to catch issues regardless of changes

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple CodeQL security analysis runs,
# as these are comprehensive security scans that shouldn't run simultaneously.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the
# latest security analysis results matter for identifying potential vulnerabilities.
# - For main branch pushes and scheduled runs, we never cancel security analysis to ensure
# complete security validation and maintain our security baseline with weekly scheduled scans.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
analyze:
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ on:
pull_request:
types: [opened, synchronize, reopened, edited, ready_for_review]

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple Danger runs on the same PR,
# as Danger performs PR validation and comment generation that should be based on the latest code.
# - We always cancel in-progress runs since this workflow only runs on pull requests, and only
# the latest commit's validation results matter for providing accurate PR feedback and reviews.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
danger:
name: Danger
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ on:
- "fastlane/**"
- "Gemfile.lock"

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple CocoaPods integration tests,
# as these tests involve complex dependency resolution and build processes.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the
# latest integration test results matter for validating CocoaPods compatibility.
# - For main branch pushes, we never cancel integration tests to ensure our CocoaPods
# integration remains functional for all downstream consumers relying on our library.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
test:
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/lint-clang-formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ on:
- "Makefile" # Make commands used for formatting setup
- "Brewfile*" # Tools installation affects formatting environment

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple lint runs on the same code,
# as linting checks are deterministic and don't require parallel validation.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the
# latest linting matters for merge decisions.
# - For main branch pushes, we never cancel formatting checks to ensure our code maintains consistent style
# standards across the entire project.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
# While ubuntu runners have clang-format preinstalled, they use an older version. We want to use the most recent one,
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/lint-cocoapods-specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ on:
- "Brewfile*" # Tools installation affects linting environment
- ".swiftlint.yml"

# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple lint runs on the same code,
# as linting checks are deterministic and don't require parallel validation.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the
# latest linting matters for merge decisions.
# - For main branch pushes, we never cancel formatting checks to ensure our code maintains consistent style
# standards across the entire project.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint-podspec:
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/lint-dprint-formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ on:
- ".github/workflows/lint-dprint-formatting.yml"
- "dprint.json"

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple lint runs on the same code,
# as linting checks are deterministic and don't require parallel validation.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the latest
# linting matters for merge decisions.
# - For main branch pushes, we never cancel formatting checks to ensure our code maintains consistent
# style standards across the entire project.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Lint
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/lint-shellcheck-formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ on:
- ".github/workflows/lint-shellcheck-formatting.yml"
- "**/*.sh"

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple lint runs on the same code,
# as linting checks are deterministic and don't require parallel validation.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the latest
# linting matters for merge decisions.
# - For main branch pushes, we never cancel formatting checks to ensure our code maintains consistent
# style standards across the entire project.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Lint
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/lint-swift-formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ on:
- "Makefile" # Make commands used for formatting setup
- "Brewfile*" # Tools installation affects formatting environment

# Concurrency configuration:
# - We use workflow-specific concurrency groups to prevent multiple lint runs on the same code,
# as linting checks are deterministic and don't require parallel validation.
# - For pull requests, we cancel in-progress runs when new commits are pushed since only the latest
# linting matters for merge decisions.
# - For main branch pushes, we never cancel formatting checks to ensure our code maintains consistent
# style standards across the entire project.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Lint
Expand Down
Loading
Loading