Problem
Every push to a feature branch triggers full MSBuild build + test + coverage (~9 min of Windows runner time). This burns GitHub Actions minutes on work that will be validated again when a PR is opened. CodeQL also runs on every master push/PR unnecessarily.
Goal
Minimize CI minutes on routine development work while maintaining full validation before merge.
Proposed Workflow Layout
| Workflow |
Trigger |
What it does |
Time |
| lint.yml (new) |
Push to any branch + PRs |
Format + style + Roslyn analyzers (no build) |
~2 min |
| ci.yml |
PRs + push to master/pre-release |
Full build + package + release |
~4-5 min |
| test.yml |
PRs only |
Full build + test + coverage + PR comment |
~5 min |
| codeql |
Weekly (if changes) + manual |
Security analysis |
~14 min |
| format.yml |
Delete |
Replaced by lint.yml |
- |
Cost Comparison
| Event |
Before |
After |
| Push to feature branch |
~9 min (full build + test) |
~2 min (lint only) |
| PR opened/updated |
~11 min |
~11 min (same, this is where it matters) |
| PR merged to pre-release |
~10 min |
~5 min (ci only, no test re-run) |
| PR merged to master |
~24 min (ci + test + codeql) |
~5 min (ci only) |
| Weekly (if changes) |
- |
~14 min (codeql, conditional) |
Estimated monthly savings: 50-70% reduction in Windows runner minutes.
Implementation Details
1. lint.yml — Lightweight source checks (no build)
Uses dotnet format subcommands which run Roslyn analyzers in-memory without producing binaries:
dotnet restore # cached, ~10s
dotnet format whitespace --verify-no-changes # indentation, line endings
dotnet format style --verify-no-changes # IDE* rules (naming, var usage)
dotnet format analyzers --verify-no-changes # CA* + RCS* rules
Results posted to:
- PR comment (if triggered by PR)
- Issue comment (if branch name matches
{number}-{slug} pattern from gh issue develop)
- Job summary (always, visible in Actions tab)
2. ci.yml — Remove feature branch push trigger
Change from:
on:
push:
branches: [master, pre-release, feature/**, fix/**]
To:
on:
push:
branches: [master, pre-release]
pull_request:
branches: [master, pre-release]
Feature/fix branch pushes no longer trigger a full build.
3. test.yml — PR only
Change from push + PR triggers to PR only. Tests already ran during PR — no need to re-run on merge push.
4. CodeQL — Weekly conditional + manual
on:
schedule:
- cron: "0 3 * * 1" # Every Monday 3am
workflow_dispatch: # Manual trigger from Actions tab
steps:
- name: Check for recent commits
run: |
# Skip if no commits in last 7 days
count=$(git log --oneline --since="7 days ago" | wc -l)
if [ "$count" -eq 0 ]; then echo "skip=true"; fi
Removes push/PR triggers entirely. Only runs weekly on master if there were actual changes, or manually when needed.
5. Delete format.yml
Replaced by lint.yml which covers formatting + style + analyzers in one workflow.
6. Developer workflow update (CONTRIBUTING.md)
Document the recommended flow using GitHub's built-in branch creation:
gh issue develop 54 --checkout # creates branch 54-nuget-packages
# ... work, commit, push ...
# → lint runs in ~2 min, posts results to issue #54
gh pr create --fill # opens PR, full CI kicks in
Branch naming follows GitHub convention: {issue-number}-{slugified-title}. The lint workflow parses the issue number from the branch name to post comments on the linked issue.
Reasoning
- Feature branch pushes are exploratory — developers push frequently, full CI is wasted
- PRs are the quality gate — that's where full validation matters
- Roslyn analyzers catch real issues — naming, complexity, code quality — without building
- CodeQL is expensive and slow — weekly is sufficient for a project this size
- GitHub's branch naming is battle-tested — no custom scripts needed, auto-sanitized
- Issue comments from lint give developers feedback without opening a PR first
Related
Problem
Every push to a feature branch triggers full MSBuild build + test + coverage (~9 min of Windows runner time). This burns GitHub Actions minutes on work that will be validated again when a PR is opened. CodeQL also runs on every master push/PR unnecessarily.
Goal
Minimize CI minutes on routine development work while maintaining full validation before merge.
Proposed Workflow Layout
Cost Comparison
Estimated monthly savings: 50-70% reduction in Windows runner minutes.
Implementation Details
1. lint.yml — Lightweight source checks (no build)
Uses
dotnet formatsubcommands which run Roslyn analyzers in-memory without producing binaries:Results posted to:
{number}-{slug}pattern fromgh issue develop)2. ci.yml — Remove feature branch push trigger
Change from:
To:
Feature/fix branch pushes no longer trigger a full build.
3. test.yml — PR only
Change from push + PR triggers to PR only. Tests already ran during PR — no need to re-run on merge push.
4. CodeQL — Weekly conditional + manual
Removes push/PR triggers entirely. Only runs weekly on master if there were actual changes, or manually when needed.
5. Delete format.yml
Replaced by lint.yml which covers formatting + style + analyzers in one workflow.
6. Developer workflow update (CONTRIBUTING.md)
Document the recommended flow using GitHub's built-in branch creation:
Branch naming follows GitHub convention:
{issue-number}-{slugified-title}. The lint workflow parses the issue number from the branch name to post comments on the linked issue.Reasoning
Related