The 45-Day Cleanup: How GitHub Killed the Orphan Repository
Site Owner
Published on 2026-07-10
How GitHub engineered its way out of 11,000 unowned repositories in 45 days — custom properties, a CronJob-driven enforcement loop, two real incidents, and a recipe any platform team can copy.
The 45-Day Cleanup: How GitHub Killed the Orphan Repository
GitHub's engineering org has over 14,000 repositories in its primary internal organization. As of early 2025, around 11,000 were non-archived. The vast majority had no clear owner (source: GitHub Blog, 2026-07-09).
That isn't a hygiene problem. It's a slow-burn security and reliability liability that surfaces the day you actually need to act. GitHub felt it during their secret scanning remediation effort: rotating a leaked token is easy when you know who owns the repo, hard when you don't. They spent the next month and a half fixing it for good.
The fix wasn't a new tool. It was a property, enforced at creation time, backed by a guardrailed loop that knows when to bail out. The result: 3,000 active repos, 11,000 archived, and an architecture that any mid-to-large engineering org can copy.
The original ownership model — and the gap it left
GitHub has long tracked ownership for production services through an internal Service Catalog. Each entry tied a service to its repository, owning team, executive sponsor, and support metadata. That mapping was rich, and it powered incident response, on-call routing, vulnerability management, and compliance scoping (source: GitHub Blog).
The relationship was many-to-one: a service could attach to a single repository, but a single repository could host multiple services. That worked if you started from the service and walked to the repo. It broke if you started from the repo and walked backwards — because the lookup only worked for repos that already mapped to a service in the first place.
Everything else fell into the gap. Team repos. Documentation repos. Internal tools. One-off project repos. Personal experiment repos. Anything that didn't back a deployed service.
#开源#团队管理
For a one-off cleanup, that ambiguity is annoying but manageable. You check commit history, read the README, ask in Slack, or guess from the repo name. For recurring security workflows that fan out across the entire org, the gap is a real risk. GitHub's secret scanning cleanup proved it: too much time spent finding the right owners before any alert could be routed.
Orphan repos are invisible until something forces you to look. That's the trap.
Designing ownership as a first-class property
The fix had to start at the data layer. Repository ownership needed to be queryable, enforceable, and stable across the org — not a sidecar file in each repo, not a centralized config repo nobody reads.
GitHub chose custom properties at the organization level. Two of them:
ownership-type — a single-select with three values: Service Catalog, Hubber Handle (a "Hubber" is what GitHub calls its employees), and Team.
ownership-name — a text field with light validation enforced by a GitHub App.
The validation rules are worth reading carefully. Hubber handles were checked against actual membership in the org. Teams were verified to exist and have at least two members. Service Catalog entries were confirmed against the catalog itself (source: GitHub Blog).
The team size floor isn't arbitrary. A repo owned by a single person is fragile: when that person leaves, the repo's owner disappears with them. A team needs at least two members to be a real, durable owner. Anything critical enough to outlast a single person should be owned by a team or a service, not an individual. That's a design principle, not just a validation rule.
Formatting was intentionally permissive. If someone typed @my-team instead of my-team, the App accepted it. The goal was to make adding ownership frictionless, then lean on validation to catch the actually invalid cases: nonexistent teams, former employees, decommissioned services.
Day-one coverage and the 30-day grace loop
The rollout had two phases: backfill, then enforce.
Backfill. Before any human was asked to do anything, GitHub built a periodic sync from the Service Catalog to repository custom properties. Every repo that backed a known service had its ownership-type set to "Service Catalog" and ownership-name populated automatically. That covered ~1,500 service-backed repos on day one (source: GitHub Blog).
The rest — team repos, docs repos, personal experiments, hackathon leftovers — fell to human action.
Enforce. A GitHub App backed by a Kubernetes CronJob scanned every repo for missing ownership. Repos without it got a warning issue, then a 30-day grace, then archival. Archiving was the chosen enforcement verb because it's reversible and non-destructive: the repo becomes read-only, Actions stop running, nothing is deleted. If someone needs it back, they unarchive, set ownership, and continue. That made it safe to apply archival broadly instead of debating every edge case (source: GitHub Blog).
The first CronJob run was scheduled for a Saturday morning. "Nobody would be paying attention," the reasoning went.
Big mistake. Issues landed across the org within hours. People jumped on Slack asking why their repo had a new issue saying it would be archived. At a globally distributed company, someone is always online.
The 30-day grace did its job: by the deadline, every active repo had a validated owner or got archived. After steady state, the loop tightened from 30 days to one hour for any new repo that somehow bypassed creation-time ownership enforcement. Faster detection, but only because the backfill and grace had already done the heavy lifting.
Two incidents, and the guardrails they forced
The rollout was mostly smooth. Two minor internal incidents exposed edge cases the original design didn't cover.
Incident A — notifications that nobody saw. Datadog had been configured to open issues in a repository as part of a monitoring workflow. When the repo was archived, Datadog couldn't create the issue. The internal monitoring service noticed the failure and paged the owning team, who escalated to the repo-ownership team.
The root cause wasn't archival — it was that the ownership issues landed in the repo, but no human got pinged directly. If you don't watch the issues tab, you miss the message. Fix: @-mention repository administrators on ownership issues, and assign all users with write access as a fallback. That way the issues couldn't be buried, and the people who could actually set ownership saw them immediately (source: GitHub Blog).
Incident B — bad data nearly mass-archived valid repos. GitHub had built robustness against a Service Catalog outage. They hadn't built robustness against the catalog returning stale or corrupted data. If bad data made the App think a batch of repos had lost their Service Catalog entries when they hadn't, the next run would mass-archive repositories with perfectly valid owners.
The fix was a low water mark. Before any action, the App tallies how many archives it's about to perform and how many issues it's about to open. If the count exceeds a conservative threshold, the run bails out entirely and triggers a Datadog monitor. If the Service Catalog is unreachable, the job skips Service Catalog validation and only checks what it can verify independently (source: GitHub Blog).
Both fixes share the same shape: an automation loop without a bail-out threshold is just a faster way to do the wrong thing at scale. Assume your data sources will be wrong. Assume your notifications will get lost. Design for both before you trust the loop.
Results, in the numbers GitHub reported
The final shape of the org after the 45-day effort:
~3,000 active repositories (down from ~11,000).
~11,000 archived (up from ~3,000).
Under 45 days from first run to steady state.
Every active repository has a validated owner, or it gets archived (source: GitHub Blog).
Many of the newly archived repos hadn't seen a commit in years. Abandoned experiments. Completed hackathon projects. One-person prototypes from 2008. Archiving them reduced the active surface area and made the active inventory reflect reality — instead of a curated fiction that drifted further from the truth every quarter.
What this means for your org
You can copy this architecture today, using GitHub Custom Properties. GitHub's recommended order of operations:
Define your ownership taxonomy. Decide which owner types make sense — services, teams, individuals, or something else. Three was the right number for GitHub; pick what fits your org. Whatever you pick, write down a one-line definition for each. "Team" needs a floor (GitHub picked two members). "Service" needs to map to whatever passes for your service catalog. "Individual" needs an expiry — if the owner leaves, who inherits?
Create custom properties at the organization level.ownership-type as a single-select, ownership-name as text. Both queryable through the API. The single-select is the part that matters most: it gives you a closed vocabulary that downstream automation can rely on. Free-form ownership fields rot; enumerated fields don't.
If you have a service catalog or asset inventory, sync it. Day-one coverage from existing data beats asking humans to fill in gaps. The reverse is also true: if your catalog has stale entries, the sync will inherit that staleness. Audit your source before you trust the destination.
Enforce ownership at repository creation time. Make the properties required. The inventory stays clean going forward. Without creation-time enforcement, every new repo is a fresh debt that you'll pay interest on later.
Build a grace-period workflow for existing repos. Issue, deadline, reversible archive. Archive is your friend because it's non-destructive. Make unarchiving easy — a single click, no support ticket — or the friction will push people to fork stale repos instead of reviving them, and you'll grow the surface area you were trying to shrink.
Don't run your first enforcement pass on a Saturday. At any globally distributed company, someone is always online. The first run is the most chaotic one — questions land, escalations start, edge cases surface. Run it when the team that built the loop is online to answer them.
Build guardrails before you trust automation at scale. Low water marks and @-mention fallbacks aren't optional. They're the difference between a rollout and an incident report. Any automation loop that performs destructive actions at scale needs a bail-out threshold and a degraded mode for when its data sources go dark. Treat those two as load-bearing, not nice-to-have (source: GitHub Blog + Custom Properties docs).
The longer arc — why orphan repos accumulate in the first place
Most engineering orgs end up with the orphan-repo problem through the same sequence. A repo gets created for a specific project or experiment. The project ships, the experiment ends, or the team moves on. The repo stays. Nothing in the creation flow asks who will own it in two years. Nothing in the deletion flow asks whether anyone still needs it.
Over a few years, the inventory grows. The org's mental model of "our repos" stays small. The two diverge quietly. Then a security incident, a compliance audit, or a dependency CVE forces a cross-cut, and the divergence becomes visible. The cost of fixing it grows linearly with how long the divergence has been allowed to widen.
The temptation is to write a one-time cleanup script. Don't. Cleanup scripts that aren't backed by creation-time enforcement just reset the clock. GitHub's own audit found archived repositories with no commits since 2008 — meaning the org had been accumulating this debt for at least fifteen years before anyone tried to pay it down. A single cleanup without a structural fix would have started the same cycle over.
The structural fix is what makes GitHub's 45-day effort durable, not heroic. The 30-day grace gets the backlog down to zero. The custom properties keep new repos from joining the backlog. The 1-hour detection loop catches bypass attempts before they metastasize. The low water mark keeps the loop from doing more damage than it prevents when something goes wrong. Each piece is small; together they form a system that compounds.
The deeper lesson isn't about GitHub specifically. It's about how platform engineering defaults get set. The cost of an orphan repo isn't paid when you create it. It's paid years later, the day a security alert lands in a repo nobody watches, a dependency needs patching in code nobody owns, or a compliance audit asks "who's responsible for this." Every repo without a durable owner is a small loan against a future incident.
The 45-day cleanup wasn't heroic. It was the inevitable payback for years of "we'll add ownership later." Most engineering orgs have a version of this debt. The architecture GitHub published is one of the more reproducible ways to clear it.