Managing GitHub Copilot Instructions Across 50 Repos Without Losing Your Mind
GitHub Copilot reads a file called .github/copilot_instructions.md in every repository. Write good instructions, and Copilot generates code that follows your conventions. Write nothing, and Copilot guesses — usually wrong.
The problem isn't writing instructions for one repo. It's maintaining them across 50. Or 200. Every repo needs the same security rules, the same coding standards, the same testing conventions. Copy-paste them manually, and they drift within a week. Someone updates the Node.js repo but forgets the Python one. Security adds a new rule but only tells three teams.
This article is the technical deep-dive into solving this. No theory — just the architecture, the code, and the gotchas we've learned from teams running this in production.
The Architecture: Central Repo + Submodules + Actions
The system has three components:
- Central standards repo — one source of truth for all Copilot instructions
- Git submodules — every repo links to the central repo
- GitHub Actions — automated daily sync and commit-level compliance
Here's how they connect:
┌─────────────────────────┐
│ copilot-standards │ ← Central repo (one per org)
│ ├── instructions/ │
│ ├── skills/ │
│ └── actions/ │
└──────────┬──────────────┘
│ submodule
┌─────┼──────┬──────────┐
▼ ▼ ▼ ▼
repo-1 repo-2 repo-3 repo-N
(.github/copilot_instructions.md)
Step 1: Structure Your Central Repo
Don't dump everything into one file. Split instructions by concern:
copilot-standards/
├── instructions/
│ ├── 00-global.md # Universal rules
│ ├── 01-security.md # Security requirements
│ ├── 02-testing.md # Testing standards
│ ├── 03-error-handling.md # Error patterns
│ └── 04-api-design.md # API conventions
├── stacks/
│ ├── typescript-next.md # Next.js + TypeScript stack
│ ├── python-fastapi.md # Python + FastAPI stack
│ └── go-grpc.md # Go + gRPC stack
├── skills/
│ ├── pr-review.md # How Copilot should review PRs
│ └── refactor.md # Refactoring guidelines
├── scripts/
│ ├── build-instructions.sh # Assembles per-repo instructions
│ └── validate.sh # Checks instruction format
└── .github/
└── workflows/
└── distribute.yml # Syncs to all repos
The numbered prefixes in instructions/ control assembly order. Security comes before testing. Global rules come first.
Writing Effective Instructions
Bad instructions:
Write good code. Follow best practices. Be secure.
Good instructions:
## Error Handling
- Wrap all async operations in try/catch blocks
- Use custom error classes that extend BaseError
- Include error codes: ERR_AUTH_FAILED, ERR_NOT_FOUND, ERR_VALIDATION
- Log errors with structured format: { code, message, context, timestamp }
- Never expose internal error details to API consumers
- Return RFC 7807 Problem Details format for HTTP errors
## Database
- Use parameterized queries exclusively — never concatenate SQL strings
- All queries must have a timeout of 30 seconds max
- Use connection pooling — max 20 connections per service
- Read replicas for GET endpoints, primary for writes
- All migrations must be reversible
Be specific. Copilot follows concrete rules much better than vague principles.
Step 2: The Build Script
Each repo needs a single .github/copilot_instructions.md file. A build script assembles it from the central repo's components:
#!/bin/bash
# scripts/build-instructions.sh
# Assembles copilot_instructions.md for a specific repo
REPO_TYPE="${1:-typescript-next}" # Stack type
OUTPUT=".github/copilot_instructions.md"
STANDARDS_DIR=".copilot-standards"
echo "# Copilot Instructions (auto-generated)" > "$OUTPUT"
echo "# Do not edit — managed by copilot-standards" >> "$OUTPUT"
echo "# Last updated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$OUTPUT"
echo "" >> "$OUTPUT"
# 1. Global instructions (numbered, in order)
for f in "$STANDARDS_DIR"/instructions/[0-9]*.md; do
[ -f "$f" ] && cat "$f" >> "$OUTPUT" && echo -e "\n" >> "$OUTPUT"
done
# 2. Stack-specific instructions
STACK_FILE="$STANDARDS_DIR/stacks/$REPO_TYPE.md"
if [ -f "$STACK_FILE" ]; then
echo "## Stack-Specific Standards" >> "$OUTPUT"
cat "$STACK_FILE" >> "$OUTPUT"
echo -e "\n" >> "$OUTPUT"
fi
# 3. Repo-specific overrides (local, not from central)
LOCAL_FILE=".github/copilot_instructions.local.md"
if [ -f "$LOCAL_FILE" ]; then
echo "## Repo-Specific Instructions" >> "$OUTPUT"
cat "$LOCAL_FILE" >> "$OUTPUT"
fi
echo "Built $OUTPUT ($(wc -w < "$OUTPUT") words)"
Teams can still add repo-specific rules in .github/copilot_instructions.local.md — the build script appends them after the global and stack instructions.
Step 3: Submodule Setup
Add the central repo as a submodule in every project:
git submodule add https://github.com/your-org/copilot-standards .copilot-standards
git commit -m "chore: add copilot standards submodule"
Then run the build script:
bash .copilot-standards/scripts/build-instructions.sh typescript-next
git add .github/copilot_instructions.md
git commit -m "chore: build copilot instructions"
The Repo Manifest
How does the sync Action know which stack each repo uses? A manifest file in the central repo:
# repos.yml
repositories:
- name: api-service
stack: typescript-next
extras: [security-strict]
- name: data-pipeline
stack: python-fastapi
extras: []
- name: mobile-backend
stack: go-grpc
extras: [security-strict, hipaa]
- name: marketing-site
stack: typescript-next
extras: []
Step 4: The Daily Sync Action
This runs in the central repo every morning:
name: Distribute Copilot Standards
on:
schedule:
- cron: '0 6 * * 1-5' # 6 AM UTC, weekdays
push:
branches: [main] # Also on merge
workflow_dispatch: # Manual trigger
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Read manifest
id: repos
run: |
echo "matrix=$(yq -o=json '.repositories' repos.yml | jq -c '.')" >> "$GITHUB_OUTPUT"
- name: Sync to repos
env:
GH_TOKEN: ${{ secrets.ORG_SYNC_PAT }}
run: |
REPOS=$(yq '.repositories[].name' repos.yml)
for REPO in $REPOS; do
STACK=$(yq ".repositories[] | select(.name == \"$REPO\") | .stack" repos.yml)
echo "Syncing $REPO (stack: $STACK)..."
git clone --depth 1 "https://x-access-token:${GH_TOKEN}@github.com/your-org/$REPO.git" "/tmp/$REPO"
cd "/tmp/$REPO"
git submodule update --init --remote .copilot-standards 2>/dev/null || \
git submodule add "https://x-access-token:${GH_TOKEN}@github.com/your-org/copilot-standards.git" .copilot-standards
bash .copilot-standards/scripts/build-instructions.sh "$STACK"
if ! git diff --quiet; then
git add -A
git commit -m "chore: sync copilot standards ($(date +%Y-%m-%d))"
git push
echo "✅ $REPO updated"
else
echo "⏭️ $REPO unchanged"
fi
cd -
done
When you merge a change to copilot-standards, every repo gets updated within minutes. The daily schedule is a safety net for repos that might have missed a push event.
Step 5: Compliance Checks on Every Commit
Add this Action to every repo (distribute it via the central repo too):
name: Copilot Standards Compliance
on: [push, pull_request]
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Check instructions exist
run: |
if [ ! -f .github/copilot_instructions.md ]; then
echo "::error::Missing .github/copilot_instructions.md"
echo "Run: bash .copilot-standards/scripts/build-instructions.sh"
exit 1
fi
- name: Check instructions are current
run: |
bash .copilot-standards/scripts/build-instructions.sh "$(cat .copilot-stack 2>/dev/null || echo typescript-next)"
if ! git diff --quiet .github/copilot_instructions.md; then
echo "::warning::Copilot instructions are outdated. The daily sync will fix this, or run the build script manually."
fi
- name: Validate instruction format
run: bash .copilot-standards/scripts/validate.sh
This catches two cases:
- Someone deleted or corrupted the instructions file → build fails
- Instructions are outdated → warning (non-blocking, daily sync fixes it)
Gotchas and Lessons Learned
Instruction size matters. Copilot has context limits. If your assembled instructions exceed ~8,000 words, Copilot may truncate them. Keep instructions concise and prioritized — the numbered prefix system ensures the most important rules come first.
Don't over-specify. If you dictate every variable name and every pattern, developers fight the system. Focus on security, architecture, and conventions that prevent real bugs. Let Copilot handle style within those guardrails.
Version your standards. Tag releases in the central repo (v1.0, v1.1). If a standard change breaks something, you can pin repos to a specific version while you fix it.
Monitor adoption. Having the infrastructure is step one. Knowing whether it's working is step two. Are developers actually getting Copilot suggestions that follow your standards? Are the suggestions being accepted or dismissed?
Measuring the Impact
You built the governance stack. How do you know it's working?
You need visibility into three things:
- Copilot adoption — are developers actually using it?
- Suggestion acceptance rates — are the governed suggestions useful?
- Code quality metrics — are fewer standards violations getting through review?
CopilotScan gives you the first two. A free, 5-minute read-only scan of your Microsoft 365 tenant that shows Copilot utilization rates, licensing efficiency, and security posture. It's the baseline measurement you need before and after deploying governance.
Build the governance stack. Measure the results. Iterate.
Related reading:
- The Enterprise Copilot Governance Stack — The architecture overview
- From Copilot Chaos to Copilot Control — The journey narrative
- Copilot Readiness Assessment Checklist — 15 pre-rollout checks