Skip to content
cccskills
FreePublished on GitHub

dotnet-gha-patterns

Composes GitHub Actions workflows. Reusable workflows, composite actions, matrix, caching.

The English summary is being prepared. Showing the original description.

Works withGitHub
See how to install

Files included (1)

  • SKILL.md16.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

dotnet-gha-patterns

Composable GitHub Actions workflow patterns for .NET projects: reusable workflows with workflow_call, composite actions for shared step sequences, matrix builds across TFMs and operating systems, path-based triggers, concurrency groups for duplicate run cancellation, environment protection rules, NuGet and SDK caching strategies, and workflow_dispatch inputs for manual triggers.

Version assumptions: GitHub Actions workflow syntax v2. actions/setup-dotnet@v4 for .NET 8/9/10 support. actions/cache@v4 for dependency caching.

Scope

  • Reusable workflows with workflow_call
  • Composite actions for shared step sequences
  • Matrix builds across TFMs and operating systems
  • Path-based triggers and concurrency groups
  • NuGet and SDK caching strategies
  • workflow_dispatch inputs for manual triggers

Out of scope

  • Starter CI/CD templates -- see [skill:dotnet-add-ci]
  • CLI release pipelines (tag-triggered build-package-release for CLI tools) -- see [skill:dotnet-cli-release-pipeline]
  • Benchmark CI workflows -- see [skill:dotnet-ci-benchmarking]
  • Azure DevOps pipeline patterns -- see [skill:dotnet-ado-patterns]
  • Build/test specifics -- see [skill:dotnet-gha-build-test]
  • Publishing workflows -- see [skill:dotnet-gha-publish]
  • Deployment patterns -- see [skill:dotnet-gha-deploy]

Cross-references: [skill:dotnet-add-ci] for starter templates that these patterns extend, [skill:dotnet-cli-release-pipeline] for CLI-specific release automation, [skill:dotnet-ci-benchmarking] for benchmark-specific CI integration.


Reusable Workflows (workflow_call)

Defining a Reusable Workflow

Reusable workflows allow callers to invoke an entire workflow as a single step. Define inputs, outputs, and secrets for a clean contract:


# .github/workflows/build-reusable.yml
name: Build (Reusable)

on:
  workflow_call:
    inputs:
      dotnet-version:
        description: '.NET SDK version to install'
        required: false
        type: string
        default: '8.0.x'
      configuration:
        description: 'Build configuration'
        required: false
        type: string
        default: 'Release'
      project-path:
        description: 'Path to solution or project file'
        required: true
        type: string
    outputs:
      artifact-name:
        description: 'Name of the uploaded build artifact'
        value: ${{ jobs.build.outputs.artifact-name }}
    secrets:
      NUGET_AUTH_TOKEN:
        description: 'NuGet feed authentication token'
        required: false

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-name: build-${{ github.sha }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ inputs.dotnet-version }}

      - name: Restore
        run: dotnet restore ${{ inputs.project-path }}

      - name: Build
        run: dotnet build ${{ inputs.project-path }} -c ${{ inputs.configuration }} --no-restore

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: |
            **/bin/${{ inputs.configuration }}/**
          retention-days: 7

```text

### Calling a Reusable Workflow

```yaml

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    uses: ./.github/workflows/build-reusable.yml
    with:
      dotnet-version: '8.0.x'
      project-path: MyApp.sln
    secrets:
      NUGET_AUTH_TOKEN: ${{ secrets.NUGET_AUTH_TOKEN }}

  test:
    needs: build
    uses: ./.github/workflows/test-reusable.yml
    with:
      dotnet-version: '8.0.x'
      project-path: MyApp.sln

```yaml

### Cross-Repository Reusable Workflows

Reference workflows from other repositories using the full path:

```yaml

jobs:
  build:
    uses: my-org/.github-workflows/.github/workflows/dotnet-build.yml@v1
    with:
      dotnet-version: '9.0.x'
    secrets: inherit # pass all secrets from caller

```yaml

Use `secrets: inherit` when the reusable workflow needs access to the same secrets as the calling workflow without
explicit enumeration.

---

## Composite Actions

### Creating a Composite Action

Composite actions bundle multiple steps into a single reusable action. Use them for shared step sequences that appear
across multiple workflows:

```yaml

# .github/actions/dotnet-setup/action.yml
name: 'Setup .NET Environment'
description: 'Install .NET SDK and restore NuGet packages with caching'

inputs:
  dotnet-version:
    description: '.NET SDK version'
    required: false
    default: '8.0.x'
  project-path:
    description: 'Path to solution or project'
    required: true

runs:
  using: 'composite'
  steps:
    - name: Setup .NET SDK
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ inputs.dotnet-version }}

    - name: Cache NuGet packages
      uses: actions/cache@v4
      with:
        path: ~/.nuget/packages
        key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
        restore-keys: |
          nuget-${{ runner.os }}-

    - name: Restore dependencies
      shell: bash
      run: dotnet restore ${{ inputs.project-path }}

```bash

### Using a Composite Action

```yaml

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET environment
        uses: ./.github/actions/dotnet-setup
        with:
          dotnet-version: '9.0.x'
          project-path: MyApp.sln

      - name: Build
        run: dotnet build MyApp.sln -c Release --no-restore

```text

### Reusable Workflow vs Composite Action

| Feature          | Reusable Workflow               | Composite Action                |
| ---------------- | ------------------------------- | ------------------------------- |
| Scope            | Entire job with runner          | Steps within a job              |
| Runner selection | Own `runs-on`                   | Caller's runner                 |
| Secrets access   | Explicit or `inherit`           | Caller's context                |
| Outputs          | Job-level outputs               | Step-level outputs              |
| Best for         | Complete build/test/deploy jobs | Shared setup/teardown sequences |

---

## Matrix Builds

### Multi-TFM and Multi-OS Matrix

```yaml

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        dotnet-version: ['8.0.x', '9.0.x']
        include:
          - os: ubuntu-latest
            dotnet-version: '10.0.x'
        exclude:
          - os: macos-latest
            dotnet-version: '8.0.x'
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET ${{ matrix.dotnet-version }}
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Test
        run:
          dotnet test --framework net${{ matrix.dotnet-version == '8.0.x' && '8.0' || matrix.dotnet-version == '9.0.x'
          && '9.0' || '10.0' }}

```text

**Key decisions:**

- `fail-fast: false` ensures all matrix combinations run even if one fails, giving full signal on which platforms/TFMs
  are broken
- `include` adds specific combinations not in the Cartesian product
- `exclude` removes combinations that are unnecessary or unsupported

### Dynamic Matrix from JSON

Generate matrix values dynamically for complex scenarios:

```yaml

jobs:
  compute-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
      - id: set-matrix
        shell: bash
        run: |
          set -euo pipefail
          # Extract TFMs from Directory.Build.props or csproj files
          TFMS=$(grep -rh '<TargetFrameworks\?>' **/*.csproj | \
            sed 's/.*<TargetFrameworks\?>//' | sed 's/<.*//' | \
            tr ';' '\n' | sort -u | jq -R . | jq -sc .)
          echo "matrix={\"tfm\":$TFMS}" >> "$GITHUB_OUTPUT"

  test:
    needs: compute-matrix
    strategy:
      matrix: ${{ fromJson(needs.compute-matrix.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test --framework ${{ matrix.tfm }}

```text

---

## Path-Based Triggers

### Selective Workflow Execution

Trigger workflows only when relevant files change. Reduces CI cost and feedback time:

```yaml

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'tests/**'
      - '*.sln'
      - 'Directory.Build.props'
      - 'Directory.Packages.props'
      - '.github/workflows/ci.yml'
  pull_request:
    branches: [main]
    paths:
      - 'src/**'
      - 'tests/**'
      - '*.sln'
      - 'Directory.Build.props'
      - 'Directory.Packages.props'

```xml

### Ignoring Non-Code Changes

Use `paths-ignore` to skip builds for documentation-only changes:

```yaml

on:
  push:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '*.md'
      - 'LICENSE'
      - '.editorconfig'

```markdown

**Choose `paths` or `paths-ignore`, not both.** When both are specified on the same event, `paths-ignore` is ignored.
Use `paths` (allowlist) for focused workflows; use `paths-ignore` (denylist) for broad workflows.

---

## Concurrency Groups

### Cancelling Duplicate Runs

Prevent wasted CI time by cancelling in-progress runs when new commits are pushed to the same branch or PR:

```yaml

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

```yaml

### Environment-Scoped Concurrency

Prevent parallel deployments to the same environment:

```yaml

concurrency:
  group: deploy-production
  cancel-in-progress: false # queue, do not cancel deployments

```yaml

Use `cancel-in-progress: true` for build/test (newer commit supersedes older), but `cancel-in-progress: false` for
deployments (do not cancel an in-progress deploy).

---

## Environment Protection Rules

### Configuring Environments

```yaml

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Deploy to staging
        run: echo "Deploying..."

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Deploy to production
        run: echo "Deploying..."

```text

Configure protection rules in GitHub Settings > Environments:

| Rule                               | Purpose                                        |
| ---------------------------------- | ---------------------------------------------- |
| Required reviewers                 | Manual approval before deployment              |
| Wait timer                         | Cooldown period (e.g., 15 minutes)             |
| Branch restrictions                | Only `main` or `release/*` branches can deploy |
| Custom deployment protection rules | Third-party integrations (monitoring checks)   |

### Environment Secrets

Environments can have their own secrets that override repository-level secrets. Use environment-scoped secrets for
deployment credentials:

```yaml

jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        env:
          # These resolve to environment-specific values
          CONNECTION_STRING: ${{ secrets.CONNECTION_STRING }}
          API_KEY: ${{ secrets.API_KEY }}
        run: ./deploy.sh

```text

---

## Caching Strategies

### NuGet Package Cache

```yaml

- name: Cache NuGet packages
  uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
    restore-keys: |
      nuget-${{ runner.os }}-

```csharp

The `restore-keys` prefix match ensures a partial cache hit when csproj files change (most packages remain cached).

### .NET SDK Cache

For self-hosted runners or scenarios where SDK installation is slow:

```yaml

- name: Setup .NET with cache
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'
    cache: true
    cache-dependency-path: '**/packages.lock.json'

```json

The `cache: true` option in `actions/setup-dotnet@v4` enables built-in NuGet caching using `packages.lock.json` as the
cache key.

### Build Output Cache (.NET 9+)

.NET 9 introduced MSBuild build-check caching. For incremental CI builds:

```yaml

- name: Cache build output
  uses: actions/cache@v4
  with:
    path: |
      **/bin/
      **/obj/
    key: build-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.cs') }}
    restore-keys: |
      build-${{ runner.os }}-

```csharp

Use build output caching cautiously -- stale caches can mask build errors. Prefer NuGet caching as the primary CI speed
optimization.

---

## `workflow_dispatch` Inputs

### Manual Trigger with Parameters

```yaml

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target deployment environment'
        required: true
        type: choice
        options:
          - staging
          - production
        default: staging
      version:
        description: 'Version to deploy (e.g., 1.2.3)'
        required: true
        type: string
      dry-run:
        description: 'Simulate deployment without applying changes'
        required: false
        type: boolean
        default: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: v${{ inputs.version }}

      - name: Deploy
        env:
          DRY_RUN: ${{ inputs.dry-run }}
        run: |
          set -euo pipefail
          if [ "$DRY_RUN" = "true" ]; then
            echo "DRY RUN: would deploy v${{ inputs.version }} to ${{ inputs.environment }}"
          else
            ./deploy.sh --version ${{ inputs.version }}
          fi

```text

Input types: `string`, `boolean`, `choice`, `environment` (selects from configured environments).

---

## Agent Gotchas

1. **Do not mix `paths` and `paths-ignore` on the same event** -- when both are specified, `paths-ignore` is silently
   ignored. Use one or the other.
2. **Set `fail-fast: false` on matrix builds** -- default `fail-fast: true` cancels sibling jobs when one fails, hiding
   which other combinations also break.
3. **Use `set -euo pipefail` in all bash steps** -- without `pipefail`, a non-zero exit from a piped command (e.g.,
   `script | tee`) does not fail the step.
4. **Reusable workflow inputs are strings by default** -- boolean and number types must be explicitly declared with
   `type:` in the workflow_call inputs.
5. **Cache keys must include `runner.os`** -- NuGet packages are OS-dependent; a Linux-built cache restoring on Windows
   causes restore failures.
6. **Do not hardcode TFMs in workflow files** -- use matrix variables or extract from csproj to keep workflows in sync
   with project configuration.
7. **`secrets: inherit` passes all caller secrets** -- use explicit secret declarations for security-sensitive reusable
   workflows to limit exposure.
8. **Concurrency groups for deploys must use `cancel-in-progress: false`** -- cancelling an in-progress deployment can
   leave infrastructure in an inconsistent state.

Code Navigation (Serena MCP)

Primary approach: Use Serena symbol operations for efficient code navigation:

  1. Find definitions: serena_find_symbol instead of text search
  2. Understand structure: serena_get_symbols_overview for file organization
  3. Track references: serena_find_referencing_symbols for impact analysis
  4. Precise edits: serena_replace_symbol_body for clean modifications

When to use Serena vs traditional tools:

  • Use Serena: Navigation, refactoring, dependency analysis, precise edits
  • Use Read/Grep: Reading full files, pattern matching, simple text operations
  • Fallback: If Serena unavailable, traditional tools work fine

Example workflow:

# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"

# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Patterns and techniques for evaluating and improving AI agent outputs.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

AI-powered wiki generation for code repositories with commands, agents, and skills

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

See all skills by rudironsoni

Report a problem with this skill