The challenge of maintaining multiple repositories
When you maintain one or two repositories, duplicating a few configuration files doesn't seem like a big deal.
A CONTRIBUTING.md here, a pull request template there, some security policies, and a few GitHub Actions workflows. Everything works, and development continues.
The problem becomes more noticeable as the number of repositories grows.
A security improvement implemented in one project never reaches the others. Workflows evolve, but several repositories continue using older versions. Contribution guidelines become inconsistent, and dependencies require individual updates.
Over time, these differences create configuration drift.
I started addressing this problem by evolving my .github repository into a central governance and automation layer for the projects I maintain.
The goal wasn't to move every configuration into a single repository or eliminate each project's autonomy. I wanted to establish shared standards, reduce duplication, and automate recurring maintenance tasks while preserving repository-specific requirements.
In this article, I'll explain how that structure works, what I implemented, and the architectural decisions behind it.
What makes a .github repository special?
GitHub provides special behavior for public repositories named .github.
They can contain default community health files that GitHub uses across other public repositories belonging to the same account or organization.
This includes files such as CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, SUPPORT.md, issue templates, and pull request templates.
When a repository doesn't provide its own version of a supported file, GitHub can use the corresponding default.
The important part is that local configurations take precedence.
If a project requires a different security policy or pull request template, it can maintain its own version.
This creates a useful governance model: shared defaults with local overrides when necessary.
I initially used this functionality to provide consistent contribution guidelines, security policies, and support documentation.
As my projects evolved, I realized several other responsibilities could benefit from a similar approach.
From shared files to a governance layer
Maintaining multiple repositories involves more than keeping documentation consistent.
Dependency updates, SDK versions, security checks, pipeline validation, and recurring maintenance tasks often require similar implementations.
Instead of handling each concern independently, I started using the .github repository as a coordination point.
Today, it includes community health files, reusable workflows, maintenance automation, security policies, validation scripts, and a versioned registry of instructions and skills for development agents.
There is an important distinction here.
GitHub doesn't automatically distribute every file stored in a .github repository to other projects.
Native inheritance applies only to specific supported features. Workflows, Dependabot configurations, custom scripts, and AI agent instructions require their own reuse or distribution mechanisms.
My implementation combines native GitHub features with automation built using GitHub Actions and the GitHub API.
The .github repository provides the foundation, but the broader governance model is something we build on top of it.
Reusable workflows and centralized policies
One of the first responsibilities I centralized was the implementation of policies used by multiple CI/CD pipelines.
GitHub Actions supports reusable workflows, allowing one workflow to call another.
Imagine several repositories that need to perform the same security scan.
Instead of maintaining independent copies of the scanning logic, we can expose a reusable workflow that each project calls from its own pipeline.
A simplified example:
YAML
name: Security
on:
pull_request:
jobs:
security:
uses: owner/.github/.github/workflows/reusable-secret-scan.yml@v1
Here, owner represents the account maintaining the central repository, and v1 represents a published version of the reusable workflow.
The consuming repository decides when the scan runs and how it fits into its development process.
The central repository maintains the shared implementation.
The application still owns its pipeline. The central repository owns the shared policy implementation.
In my project, one example is a reusable secret-scanning workflow designed to detect potential credentials and other sensitive information in Git history, independently of the programming language used by the consumer.
Why versioning matters
When multiple repositories depend on a shared workflow, changes can affect several consumers.
Pointing every repository to the central main branch may be convenient, but it creates a dependency on its latest state.
That's why I implemented an explicit versioning and release process.
The strategy supports semantic versioning, major-version references, and full commit SHA references when strict reproducibility is required.
A major-version reference allows consumers to receive compatible updates within that release channel. A full commit SHA identifies the exact code that will execute.
Reusing code doesn't eliminate coupling. It requires us to manage that coupling deliberately.
Versioning, compatibility, and change review are essential parts of maintaining shared automation.
Securing the automation itself
When a repository starts coordinating operations across other repositories, its role changes.
It can execute code, access repository metadata, create branches, and open pull requests.
This introduces additional security considerations.
One practice I adopted was pinning external GitHub Actions to their full commit SHA, identifying the exact code being executed.
I also configured Dependabot to monitor GitHub Actions dependencies and propose updates through pull requests.
However, automated updates don't mean automated merges.
Updating an Action changes the code executed by a pipeline. Depending on the workflow, that code may have access to tokens, secrets, or other sensitive resources.
For this reason, human review remains part of the process.
GitHub Apps and least privilege
I also use a GitHub App for operations involving multiple repositories.
Instead of relying on a long-lived personal access token with broad permissions, the automation uses short-lived installation access tokens.
This allows permissions to be restricted to the operations the application actually needs.
For example, my .NET repository inventory separates repository discovery from code inspection.
The discovery stage uses credentials to identify eligible repositories. The inspection stage receives only the information needed to process selected public repositories, without receiving the GitHub App's private key.
This reduces credential exposure during code inspection.
It's a practical application of the principle of least privilege: each component receives only the access required to perform its responsibility.
Automating .NET repository maintenance
Beyond shared policies, I implemented automation to help maintain my .NET repositories.
One example is SDK version synchronization.
The workflow identifies eligible repositories, checks their root-level global.json files, and determines whether an update is available under the configured compatibility policy.
When an applicable update is found, it prepares the change in a branch and opens a pull request.
The automation doesn't merge directly into the default branch. Each repository retains its normal review and integration process.
Building a centralized .NET inventory
Another workflow produces an inventory of .NET projects across repositories accessible to the configured GitHub App.
The goal isn't to modify projects but to obtain a consolidated view of their metadata.
The inventory uses DotNetRepoInspector to inspect .NET repositories using effective MSBuild metadata rather than relying exclusively on direct .csproj parsing.
This integration illustrates an architectural principle I wanted to preserve.
Each project retains a specific responsibility.
DotNetRepoInspector performs the inspection. The .github repository coordinates execution across multiple repositories and consolidates the results.
I didn't need to move the inspection implementation into the central repository to reuse its capabilities.
Automation needs engineering, too
Maintenance scripts often start small.
A few lines of Bash solve a problem, become part of a workflow, and continue running for months.
Things change when that same script starts modifying multiple repositories.
At that point, it deserves the same engineering attention we give other software components.
That's why my .github repository includes regression tests, contract validation, and quality checks using tools such as actionlint and ShellCheck.
I also implemented mechanisms for handling failures during batch operations.
If an update fails for one repository, processing can continue for the remaining repositories when it's safe to do so. Failures must still be explicitly reported.
Continuing after a recoverable error shouldn't mean hiding that error.
Automation branch ownership
Another concern involves the ownership of branches created by automation.
The existence of a branch named chore/sync-dotnet-sdk doesn't automatically mean a workflow can assume that branch belongs to it.
Someone may have created it manually, or it may contain changes that shouldn't be overwritten.
For that reason, the automation performs additional ownership and pull request state checks before reusing or updating an existing branch.
These safeguards become particularly relevant when scripts can modify multiple repositories.
Managing shared instructions for AI development agents
More recently, I encountered another form of shared configuration: instructions used by AI development agents.
Tools capable of inspecting code, implementing issues, and reviewing pull requests need to understand the rules and conventions of the projects they work on.
These instructions may cover architecture, testing strategies, security policies, and implementation procedures.
When maintaining multiple repositories, some instructions naturally overlap.
Copying the same files between projects, however, creates the same configuration drift problem we see with workflows.
To address this, I created an agent-governance directory inside the central repository.
It contains shared instructions, profiles, reusable skills, a manifest, and its own version file.
The available skills cover activities such as bug investigation, issue implementation, pull request review, security analysis, refactoring, and test coverage analysis.
The goal is to provide shared instructions while allowing individual projects to extend them according to their specific needs.
Distributing instructions to consuming repositories
Unlike community health files, AI agent instructions aren't automatically inherited from the .github repository.
A separate process is required to manage their evolution and distribution.
I implemented workflows that validate the central registry, synchronize selected skills from a controlled upstream source, and distribute approved updates to consuming repositories.
When the automation identifies a difference between the approved central version and the version used by a consumer, it can prepare a pull request containing the update.
This preserves change review, version history, and the ability to maintain repository-specific instructions.
The same principle used for reusable workflows applies here:
Centralize what is shared without removing control from the consuming projects.
What do we gain from this approach?
The main benefit isn't simply having fewer files.
It's reducing the number of places where the same decision must be maintained.
When a security policy has several independent copies, each copy can evolve differently.
With a central source and a controlled mechanism for reuse or distribution, the problem changes.
Instead of asking:
How do I keep ten configurations identical?
We can ask:
How do I maintain one shared policy while preserving compatibility with its consumers?
The second problem isn't trivial, but it's more explicit and easier to manage systematically.
It also becomes easier to distinguish responsibilities that belong to the broader repository ecosystem from those that should remain within individual projects.
Not every configuration should be centralized.
A workflow supporting a specific application's deployment process may have little value for other repositories. Similarly, some AI agent instructions may depend on architectural decisions unique to a particular project.
Centralizing everything can introduce unnecessary coupling.
That's why I treat the .github repository as a shared governance layer rather than a universal configuration repository.
The trade-offs of centralization
A centralized structure introduces responsibilities of its own.
An incorrect change to a shared policy may affect multiple repositories. An incompatible workflow update can break consuming pipelines. Automation with excessive permissions can increase the impact of a security incident.
This is why versioning, validation, review, and access control need to evolve alongside centralization.
Different types of configuration also require different sharing mechanisms.
Community health files can use GitHub's native default-file behavior. Reusable workflows must be explicitly called by consumers. Other configurations require dedicated distribution processes.
Each mechanism has its own limitations.
The goal isn't to eliminate every difference between projects. It's to make sure those differences are intentional rather than the result of neglected maintenance.
Key takeaways
Building this repository reinforced an important architectural principle: centralization and autonomy don't have to be opposing concepts.
We can establish shared standards without preventing individual projects from making their own decisions.
Reuse also requires governance. Shared workflows need versioning, automation needs testing, and distribution mechanisms need traceability and review.
Security deserves particular attention when a tool operates across multiple repositories. Short-lived credentials, restricted permissions, and separation of responsibilities help reduce the risks associated with centralized automation.
Finally, AI development instructions should be treated like other engineering artifacts. They require maintenance, versioning, and controlled update mechanisms.
The main lesson is simple: centralize policies that are genuinely shared, and keep project-specific decisions within the projects that own them.
Even a personal portfolio can provide a practical environment for exploring Platform Engineering, Developer Experience, and software governance.
Further reading
If you want to explore these concepts, the official GitHub documentation is a good starting point.
Creating a default community health file explains which files can be shared through a .github repository and how local configurations take precedence.
Reusing workflows covers reusable GitHub Actions workflows, including inputs, secrets, outputs, and reuse limitations.
Secure use reference describes security considerations for GitHub Actions, including permissions, credentials, and third-party Actions.
Creating GitHub Apps provides information about application authentication, installation permissions, and access tokens.
For a practical implementation, explore the projects linked at the beginning of this article. Their code and documentation are publicly available for anyone who wants to study the implementation, adapt individual components, or contribute.
If you found this project useful or it gave you an idea for your own repositories, consider leaving a ⭐ on the .github repository . It's a simple way to support the project.
Projects covered in this article:
Top comments (0)