How GitHub gave every repository a durable owner - The GitHub Blog
Try GitHub Copilot CLI
Attend GitHub Universe
Search
Michael Recachinas·@mrecachinas
July 9, 2026
9 minutes
Share:
GitHub has over 14,000 repositories across our primary internal GitHub organization. As of early 2025, there were over 11,000 non-archived repositories, the vast majority of which with no clear owner. For repositories attached to production services, we have historically had robust durable ownership, but for repositories with no associated service, there was no reliable way to tell who the owner is.
That gap became a recurring problem during our secret scanning remediation effort: while we could technically rotate a secret, doing so without knowing the repository owner was risky and often disruptive, and we had no clear way to route remediation work. Over the course of a month and a half, we validated ownership for every active repository, archived about 8,000 repositories that were no longer in use, and changed repository creation so that ownership was required from the start.
Our original ownership model
For years, GitHub has been tracking ownership for deployed services through our internal Service Catalog. Each service entry recorded metadata like which repository it lived in, which gave us a mapping from service to repository; the owning team; executive sponsor; and support information.
Here’s an example of the Repo Ownership app’s service ownership entry:
- team: github/repo-ownership-dev<br>repo: https://github.com/github/repo-ownership<br>name: repo-ownership<br>kind: moda<br>long_name: Repo Ownership<br>description: Service enforcing repo ownership across the org<br>maintainer: mrecachinas<br>exec_sponsor: stephanmiehe<br>...
Having this rich metadata enables service-centric workflows, such as incident response, on-call routing, vulnerability management, and compliance scoping.
Unfortunately, that relationship was many-to-one (i.e., a service could only be attached to a single repository, but a single repository could have multiple services). That meant if you started from a service, you could find the repository and its owners. But if you started from a repository and needed to find an owner, you had to reverse the lookup, and that only worked for repositories that mapped to a service in the first place.
That left a significant ownership gap that included team repositories, documentation repositories, internal tools, one-off project repositories, personal experiment repositories, and anything else that didn’t back a deployed service. Every time we needed to contact the owner of one of these “unowned” repositories, it required manual work: check the commit history, read the README, ask around in Slack, or make a guess based on the repository name.
For a one-off effort, that kind of ambiguity is annoying but manageable. For recurring security workflows that fan out across the entire organization, it presents a real risk. During our secret scanning cleanup, we spent too much time trying to find the right owners before we could make informed decisions about alerts.
Designing the new ownership model
Fundamentally, we needed repository ownership to be a first-class property. We considered storing ownership in a dedicated file within each repository or maintaining it in a centralized repository, but ultimately chose GitHub custom properties. This approach provided a native, structured, and organization-wide queryable way to manage ownership. It also enabled us to enforce enterprise and organization policies and rulesets selectively according to ownership type.
We created two custom properties: ownership-type and ownership-name.
ownership-type accepted three values: “Service Catalog,” “Hubber Handle” (a “Hubber” is what we call a GitHub employee), and “Team.” These covered the realistic range of repository ownership at GitHub. A repository either belongs to a service (with an on-call team and a defined lifecycle), a team (like a shared documentation repository or internal tool), or an individual (like a personal project or experiment).
ownership-name was a text field with light validation. Our GitHub App validated every value: Hubber handles were checked against actual membership in our GitHub organization, teams were verified to exist in the organization and have at least two members, and Service Catalog entries were confirmed against our Service Catalog itself. We were intentionally permissive on formatting. If someone typed @my-team instead of my-team, we accepted it. We wanted to make it frictionless to add ownership and lean on robust validation to catch invalid entries like nonexistent teams, former employees, and services that had been decommissioned.
Day-one coverage
Before we asked anyone to do anything, we built a periodic sync from Service Catalog to repository custom properties. Every repository that backed a known service had its...