Stop Copilot Merge Conflicts: Two-Pipeline Strategy for TypeScript & Flutter
Photo by Karsten Winegeart on Unsplash
Ship multiple Copilot-assigned issues in parallel without drowning in formatting-only conflicts.
The Pain Most Teams Hit with Copilot
Assigning GitHub issues to Copilot (or running many agent tasks at once) is powerful—but it surfaced a repeat problem in my repos:
- 🔀 Lots of parallel branches touching the same files
- 🧩 Most diffs were formatting-only, not functional changes
- ⚔️ Merge conflicts everywhere, wasting review time and momentum
The fix wasn’t “argue about style.” It was to automate formatting and enforce quality gates in CI so conflicts collapse to near-zero and reviews stay focused on behavior.
My Setup (Works Great with Copilot Agents)
- Repos: TypeScript (web/api) and Flutter (mobile)
- Workflow: Many small, well-scoped issues assigned to Copilot in parallel
- Goal: Keep branches mergeable and safe to ship fast
The Two-Pipeline Strategy
1) Auto-Format on Commit (per feature branch)
- Trigger: When Copilot (or a dev) pushes to a feature branch
- Action: Run the repo formatter, commit any changes back to that branch
- Effect: Normalizes style immediately, reducing formatting-only conflicts across branches that touch the same files
2) Validate, Build, and Test
- Trigger: PRs (and optionally pushes)
- Action: Verify formatting, analyze/lint, build, and run tests
- Effect: Guarantees speed without breaking behavior—good tests let you move fast with confidence
Key: Good test coverage is the trust layer. With solid tests, you can accept agent-generated changes quickly, knowing functionality didn’t regress.
What This Guide Gives You
- A practical structure for running many Copilot-driven issues in parallel
- Auto-format and quality-gate pipelines for TypeScript and Flutter
- Issue-planning tips to avoid cross-branch thrash
- Copy/paste CI examples you can adapt immediately
Issue Planning to Limit Conflicts
- Prefer many small issues over one large change
- Scope issues to avoid the same hotspots/files when possible
- Keep shared files (configs, index barrels, constants) stable during parallel work
- Re-run auto-format locally or via CI before opening PRs
- Rebase often to keep branches current
Implementation: TypeScript
Follow these steps in the root of your TypeScript project (or in functions/
if you’re formatting only your Firebase Functions code):
1) Install formatters (one-time)
# run this in the project folder that contains package.json
npm install --save-dev prettier eslint
Why: Prettier enforces a single, consistent style; ESLint catches code issues.
2) Create a Prettier config (one-time)
Create a file named .prettierrc.json
in the same folder as your package.json
and paste:
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
Why: This defines the exact rules your team (and Copilot) will follow to avoid “tiny formatting” diffs.
3) Add scripts to package.json (one-time)
Open package.json
and add these under the top-level scripts
key:
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . --max-warnings=0"
}
}
Why: format
rewrites files to your standard. format:check
fails CI if someone forgot to format. lint
enforces code quality.
4) Normalize your repo (before enabling CI)
npm run format
Why: This cleans up existing style drift so future diffs stay focused on behavior, not whitespace.
5) Enforce in CI
Use prettier --check .
(or npm run format:check
) in your validate workflow to fail on unformatted code.
Implementation: Flutter
Run these in the Flutter project root (the folder with pubspec.yaml
).
1) Format, analyze, and test locally
dart format . # formats files in-place
flutter analyze # static analysis (can be continue-on-error in CI)
flutter test # run your tests
Why: Establishes a consistent baseline and confirms nothing broke.
2) Optional CI-safe format check
dart format --output=none --set-exit-if-changed .
Why: This command exits with a non‑zero status if files need formatting—perfect to gate PRs. Pair it with a separate auto‑format workflow that commits formatting changes on branches, so PRs are clean.
Video: GitHub Actions Overview
GitHub Actions Pipelines
These mirror my production setups so auto‑format runs on main and dev, and CI validates before merge.
Flutter CI (build, analyze, test, verify formatting)
# .github/workflows/flutter-ci.yml
name: Flutter CI
on:
push:
branches: [ "main" , "dev" ]
pull_request:
branches: [ "main" , "dev" ]
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA to test and build (leave empty for latest)'
required: false
type: string
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: $
- name: Flutter action
uses: subosito/flutter-action@v2.13.0
- name: Install dependencies
run: flutter pub get
- name: Setup Firebase Config
run: ./setup.sh
- name: Verify formatting
run: dart format --set-exit-if-changed .
- name: Analyze project source
run: flutter analyze
continue-on-error: true
- name: Run tests
run: flutter test
Dart Auto-Format (commit formatted code back)
# .github/workflows/dart-format.yml
name: Dart Format
permissions:
contents: write
pull-requests: write
on:
push:
branches: [ "main" , "dev" ]
pull_request:
branches: [ "main" , "dev" ]
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA to format (leave empty for latest)'
required: false
type: string
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: $
fetch-depth: 0
ref: $
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Get dependencies
run: flutter pub get
- name: Run dart format
run: dart format .
- name: Check for changes
id: verify-changed-files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
- name: Commit and push formatted code
if: steps.verify-changed-files.outputs.changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "Auto-format Dart code with dart format"
git push origin HEAD:$
Auto-Format Firebase Functions (TypeScript/JavaScript)
# .github/workflows/auto-format.yml
name: Auto-Format Firebase Functions
permissions:
contents: write
pull-requests: write
on:
push:
branches: [ "main", "master", "dev" ]
pull_request:
branches: [ "main", "master", "dev" ]
workflow_dispatch:
inputs:
branch:
description: 'Branch to format (leave empty for current branch)'
required: false
type: string
reason:
description: 'Reason for manual formatting'
required: false
default: 'Manual code formatting'
type: string
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: $
fetch-depth: 0
ref: $
- name: Setup Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: '18.x'
cache: 'npm'
cache-dependency-path: functions/package-lock.json
- name: Install dependencies
run: |
cd functions
npm ci
- name: Run Prettier formatter
run: |
cd functions
npm run format
- name: Check for changes
id: verify-changed-files
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "Files were changed by formatting"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "No files were changed by formatting"
fi
- name: Commit and push formatted code
if: steps.verify-changed-files.outputs.changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
if [ "$" = "workflow_dispatch" ]; then
git commit -m "🎨 Manual auto-format: $
- Formatted TypeScript/JavaScript files in functions directory
- Applied consistent code style using Prettier
- Triggered manually on branch: $
- No functional changes, formatting only"
else
git commit -m "Auto-format Firebase Functions code with Prettier
- Formatted TypeScript/JavaScript files in functions directory
- Applied consistent code style using Prettier
- No functional changes, formatting only"
fi
git push origin HEAD:$
Firebase Functions CI (lint, build, test, formatting check)
# .github/workflows/firebase-functions-ci.yml
name: Firebase Functions CI
on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual run'
required: false
default: 'Manual trigger'
type: string
jobs:
build-and-validate:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js $
uses: actions/setup-node@v4
with:
node-version: $
cache: 'npm'
cache-dependency-path: functions/package-lock.json
- name: Install dependencies
run: |
cd functions
npm ci
- name: Run linter
run: |
cd functions
npm run lint
- name: Verify code formatting
run: |
cd functions
npx prettier --check .
- name: Build project
run: |
cd functions
npm run build
env:
NODE_ENV: test
CI: true
- name: Run tests
run: |
cd functions
npm run test:ci
echo "✅ Unit tests completed successfully"
echo "🔄 Firebase integration tests are run locally with emulators"
env:
NODE_ENV: test
CI: true
- name: Workflow Summary
if: always()
run: |
echo "## Firebase Functions CI Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Trigger:** $" >> $GITHUB_STEP_SUMMARY
if [ "$" = "workflow_dispatch" ]; then
echo "- **Manual Reason:** $" >> $GITHUB_STEP_SUMMARY
fi
echo "- **Branch:** $" >> $GITHUB_STEP_SUMMARY
echo "- **Node Version:** $" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** $" >> $GITHUB_STEP_SUMMARY
Resources
- GitHub Actions docs: https://docs.github.com/actions
- About workflows: https://docs.github.com/actions/using-workflows/about-workflows
- Workflow syntax: https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions
- Learn GitHub Actions: https://docs.github.com/actions/learn-github-actions
- GitHub Copilot: https://github.com/features/copilot (docs: https://docs.github.com/en/copilot)
- GitHub YouTube (Actions videos): https://www.youtube.com/@github
Move faster with Copilot by automating formatting and letting tests guard correctness. The calm is worth it.
Journal
- 2025-08-17 Created file
- 2205-09-03 Polished Blog