The cost of migrating off GitHub has almost nothing to do with moving your code. Git was designed to be portable — a clone carries every commit, every branch, every tag, and a push deposits all of it on any host you point it at. The bill sits in everything built around the repository: CI workflows and the runners that execute them, package registries, deploy integrations and their OIDC trust relationships, branch-protection rules, years of issue and pull-request history, installed apps and webhooks, code-owner enforcement, and single sign-on wiring.
This matters today because teams have a concrete, news-driven reason to ask the question. Cursor’s Origin code hosting entered early beta on Monday, August 17, 2026, rolling out on all paid plans — and notably, its own release keeps GitHub as the source of truth for synced repos rather than asking anyone to leave. That design choice is itself evidence about where the switching costs sit.
What follows is an inventory, not a pitch. We read GitHub’s own documentation for each layer of a typical footprint, recorded where each one physically lives, and judged honestly which are portable, which are re-buildable, and which never leave. We deliberately publish no cost figures, hour estimates, or team-size thresholds — any such number would be invented. The deliverable is a grid you can hold your own footprint against, and a decision frame for what to do about it.
- 01Git history is the one layer that leaves cleanly.A clone or push carries full source and commit history to any host. Of the ten layers we inventoried against GitHub’s own docs, it is the only one that is portable in full, function included.
- 02Most of the footprint is settings, not files.Branch rulesets, environments, secrets, webhook registrations, app installations and SSO enforcement are account or org settings in GitHub’s infrastructure — GitHub’s docs state directly that environments are stored outside version control history.
- 03Deploy trust is bound to GitHub’s identity, by design.OIDC trust relationships on AWS, Azure or GCP are configured against subject claims naming a specific GitHub org and repo. Our inference from that binding: moving hosts means reconfiguring trust on the cloud-provider side — there is nothing to copy.
- 04The richest importer we checked still documents its losses.Of the three importer pages we checked, GitLab’s is by far the most detailed. It moves issues, PRs, reviews, labels and milestones — and names what it drops: the required-status-checks rule is not imported, and comments beyond roughly 30,000 per repo are skipped by default.
- 05For most teams, coexistence beats migration.Origin’s own release describes two-way sync with GitHub, not replacement. Unless a contract or sovereignty mandate forces a move, the realistic near-term pattern is running both — with GitHub as source of truth.
01 — The News PegWhy this question, today.
The trigger is simple: a credible new code host shipped today. Cursor’s changelog states that “Origin begins rolling out today in early beta on all paid plans” — and our companion piece, our coverage of Origin’s early-beta launch, covers the release itself, so this post stays on the economics.
What makes the release relevant to a cost analysis is one sentence of Cursor’s own copy: for repos synced from GitHub, “pushes keep going to GitHub, which stays the source of truth for anything started there.” A vendor with every incentive to move workloads onto its own infrastructure chose two-way sync over migration as the on-ramp. That is not modesty; it is an engineering reading of the same inventory this post lays out. The git data is easy to sync. Everything else is not, so the product syncs the git data and leaves everything else where it is.
When a credible new host launches, teams start asking “what would it take to move?” The honest answer requires separating three different kinds of cost, layer by layer: things that travel with the repository, things that must be rebuilt by hand on the other side, and things that are not properties of the code at all and simply get reconstructed from zero. Vendor marketing tends to blur those categories. GitHub’s own documentation, read carefully, does not.
02 — The Easy PartWhat a git clone actually carries.
Start with what genuinely moves. A git clone followed by a git push to a new remote carries the complete object database: every commit with its full history, every branch, every tag, and every file at every revision. This is not a migration feature any vendor built — it is the design of git itself, which predates GitHub and treats every full clone as a peer. Nothing about this layer requires an importer, an API, or anyone’s permission.
A few configuration files ride along because they live inside the repository. GitHub Actions workflow definitions are YAML files in the repo’s .github/workflows directory, per GitHub’s own Actions documentation. CODEOWNERS is a plain file at .github/CODEOWNERS, the repo root, or docs/CODEOWNERS, mapping paths to reviewers with gitignore-style patterns. Both travel with a clone byte-for-byte.
Here is the trap: the files travel, but their function does not. A workflow YAML on a host with no Actions-compatible runner is inert text. A CODEOWNERS file only has teeth when branch protection or a ruleset requires code-owner approval — and that enforcement, per GitHub’s docs, is a repository setting layered on top of the file, not part of the file. The pattern repeats across the whole inventory: the closer a thing sits to git’s object database, the more portable it is; the closer it sits to GitHub’s account model, the less.
03 — The InventoryTen layers, three verdicts.
The grid below is the core of this post. Each row is one layer of a typical GitHub footprint. The “where it lives” column is sourced from GitHub’s own documentation for that feature, checked at the time of writing — the registry and integration rows draw on GitHub’s Packages and webhooks documentation respectively. The verdict column and the “what has to happen instead” column are our editorial synthesis on top of those sourced facts — judgment calls, clearly ours, not vendor claims. Where GitHub’s docs are silent on migration, we say so rather than inferring capability in either direction.
| Layer | Where it lives (per GitHub’s docs) | Portability verdict (ours) | What has to happen instead |
|---|---|---|---|
| Travels with the repository | |||
| Git history and code | The git object database itself — commits, branches, tags, full file history | Portable in full | A clone and a push. No importer needed; git’s own design |
| Code ownership (CODEOWNERS) | A plain file in the repo (.github/, root, or docs/); under 3 MB | File portable; function is not | Enforcement is a branch-protection setting layered on top — re-created on the new host, if it has an equivalent |
| Rebuilt by hand on the other side | |||
| CI workflows and runners | YAML in .github/workflows (in-repo); runners, marketplace actions and GITHUB_TOKEN context are GitHub infrastructure | Split — files travel, execution does not | Rewrite for the new host’s CI (GitLab’s equivalent is .gitlab-ci.yml); re-provision or re-register runners |
| Packages and registries | GitHub Packages (npm, RubyGems, Maven, Gradle, NuGet, containers); permissions inherited from the repo or set per-user/org | Re-buildable | Republish artifacts elsewhere, repoint every consumer, rebuild the permission model |
| Deploy integrations and OIDC trust | On the cloud provider’s side, bound to subject claims naming the GitHub org, repo and environment | Re-buildable, never automatic | Reconfigure the trust relationship against the new host’s identity — there is nothing to copy |
| Branch protection and rulesets | Administrative settings, not git history; up to 75 rulesets per repo; org-level rulesets are an Enterprise-plan feature | Re-buildable by hand | Re-created in the new host’s model; GitLab’s importer names the required-status-checks rule as not imported |
| Issues and PR history | GitHub’s database, referenced daily by engineers — not part of the git data | Re-buildable with loss | A rich importer (GitLab’s) moves most of it with documented gaps; a bare git migration moves none of it |
| Third-party apps and webhooks | Apps installed per-account, scoped to selected repos; webhooks registered at repo, org or app level | Re-buildable | Reinstall equivalents where they exist on the new host; re-register every webhook URL, secret and event subscription |
| Never leaves — reconstructed from zero | |||
| Environments and secrets | “Stored in GitHub’s infrastructure, not in version control history” — GitHub’s own wording on environments | Not exportable (our inference — no bulk-read path is described on the pages we checked) | Re-entered on the new host from your own vault or records — never migrated |
| Identity and SSO | An organization-account setting enforced through your IdP; repositories don’t store these policies | Not a property of the code at all | Rebuilt on the IdP side against the new host’s SSO model, from scratch |
Read as a whole, the grid explains why “we moved our repos” and “we moved off GitHub” are different sentences. One layer out of ten is portable in full. One more carries its file but not its function, and CI splits down the middle. The remaining seven are either hand-rebuilt on the destination or reconstructed from zero on a system that was never GitHub’s to begin with. The sections below walk the heaviest rows in detail, because the details are where migration plans go wrong.
04 — CI and RunnersThe workflow file travels. The factory does not.
GitHub’s own Actions documentation describes the anatomy plainly: workflows are YAML files in .github/workflows, triggered by repository events, manual dispatch or a schedule, and executed on runners — either GitHub-hosted Linux, Windows and macOS machines provisioned fresh per run, or self-hosted runners a team registers on its own infrastructure. Workflows routinely call reusable actions pulled from the GitHub Marketplace or third-party repos, referenced by name and version inside the YAML rather than vendored into it.
Decompose that into portable and non-portable parts and the split is stark. The YAML itself lives in git and travels with every clone. Everything the YAML depends on stays behind: the hosted runner fleet, the marketplace actions it references, and the GitHub-specific execution context — the automatic GITHUB_TOKEN, Actions expression syntax, event payloads. On a different host, the equivalent pipeline is a rewrite into that host’s format; GitLab’s, per its own CI/CD documentation, is a .gitlab-ci.yml file at the project root. On the GitLab CI/CD overview page we checked, no automatic converter from Actions YAML is mentioned — which is a statement about that page, not proof no tool exists anywhere.
Workflow YAML
Lives in git, so a clone carries every workflow definition byte-for-byte. On a host without an Actions-compatible executor, it is documentation of what your pipeline used to do.
The runner fleet
Hosted runners are GitHub infrastructure, provisioned per run. Self-hosted runners are your machines, but their registration is with GitHub — a new host means new registration against a new control plane.
Actions and context
Marketplace actions are referenced by name, not vendored. The token, expressions and event payloads are GitHub’s execution context. Each dependency becomes a find-an-equivalent-or-rewrite line item.
05 — Deploy TrustOIDC trust is identity-shaped, and secrets never leave.
The sharpest example of a cost that is invisible until migration day is deploy trust. GitHub’s OIDC security-hardening documentation describes the modern pattern: “After you have established a trust connection with a cloud provider that supports OIDC, you can configure your workflow to request a short-lived access token directly from the cloud provider” — no long-lived stored secret at all. It is genuinely better security, and it is the recommended practice across AWS, Azure and GCP deployments.
But look at where the trust actually lives. The relationship is configured on the cloud provider’s side, bound to a subject claim that identifies a specific GitHub org, repository and optionally environment — GitHub’s own documented example claim is repo:octo-org/octo-repo:environment:prod. That string is GitHub-identity-shaped. Move the code to another host and the cloud provider has never heard of the new identity; the trust relationship is not copied, exported or translated. It is reconfigured, deployment by deployment, on the AWS, Azure or GCP side — an inference from how the subject-claim binding works, and consistent with how OIDC federation works generally.
Sitting next to OIDC are environments and secrets. GitHub’s deployment documentation states that environments — with their protection rules, wait timers, up to six required reviewers, and environment-scoped secrets — are “stored in GitHub’s infrastructure, not in version control history.” And on the pages we checked, GitHub describes no bulk-read or export path for secrets: the documented model is write-only — set, update, delete. We flag the conclusion as our inference rather than a vendor sentence, but it matches how secret stores are deliberately built: if your team’s secrets exist only inside GitHub, the migration step is re-entering them from your own vault or records, not exporting them.
repo:octo-org/octo-repo:environment:prod — which means every deployment target holds a GitHub-shaped lock on its door. None of it blocks a migration. All of it is work that appears on no repository-size estimate.06 — GovernanceRules, owners and SSO live in the account, not the code.
Governance is where years of accumulated team decisions hide. GitHub’s rulesets documentation describes what these settings can require: passing status checks, a minimum number of PR reviews, signed commits, linear history, restrictions on force-pushes and branch or tag deletion. None of it is git data — rulesets and the older branch protection rules are administrative settings. The docs also note that “Multiple rulesets can apply to the same branch at the same time, while only one branch protection rule applies,” with the more restrictive setting winning where both exist — and that rulesets targeting multiple repositories at the org level are a GitHub Enterprise-plan feature. A repository can carry up to 75 rulesets. Every one of them would be re-expressed by hand in a new host’s governance model, which will not map one-to-one.
CODEOWNERS, covered in Section 02, is the near-miss of the inventory: the file travels, the enforcement does not. GitHub’s code-owners documentation is explicit that the file alone auto-requests review, while making code-owner approval a merge gate is a separate protection setting — and that where several owners are listed, “an approval from any of the owners is sufficient.”
Identity sits above all of it. GitHub’s SAML documentation states that with SSO enforced, “All members of the organization must authenticate through your IdP to access the organization’s resources,” and that repositories don’t store or manage these authentication policies — with an enterprise-level SAML requirement able to override org-level configuration on Enterprise accounts. SSO wiring is a relationship between your identity provider and your GitHub organization. On a new host it is not migrated, translated or lossily imported; it is built again, against a different SSO implementation, from zero.
Per repository
GitHub’s documented ceiling on rulesets a single repo can carry. Each active one is a governance decision that exists only as a GitHub setting — re-expressed by hand in any new host’s model.
Per environment
GitHub’s documented maximum for required reviewers on a deployment environment — one of several protection rules stored, per GitHub’s own wording, in its infrastructure rather than version control.
File size ceiling
The one governance artifact that lives in the repo itself and travels with a clone. Its enforcement as a merge gate, however, is a branch-protection setting that stays behind.
07 — The Importer EvidenceWhat importers actually move.
The best evidence for what portability looks like in practice is not a thought experiment — it is what a mature importer documents about itself. Of the three importer pages we checked, GitLab’s is by far the most detailed. GitLab’s GitHub importer, per GitLab’s own docs, moves a substantially wider slice than a bare clone: branches and git data, issues, pull requests as merge requests, comments, reviewers, reviews and review comments, labels, milestones, release notes, attachments and Git LFS objects. That list is the strongest documented proof that most issue and PR history can move — with effort, to a host that built the machinery.
The same page is equally valuable for what it concedes. GitLab states plainly that “The Require status checks to pass before merging GitHub rule is not imported” — a branch-protection setting singled out by name as left behind. The importer skips comments beyond roughly 30,000 per repo by default, due to GitHub API limitations, and cannot always tell whether a GitHub cross-reference points to an issue or a PR, limiting link reconstruction. GitHub Enterprise Cloud’s custom repository roles are explicitly unsupported and “cause partially completed imports.” And on the importer page we fetched, GitLab’s docs do not mention importing Actions workflows, GitHub Packages, webhooks or GitHub App installations — an absence on that page, not a statement that it can never be done.
The other two importers frame the range. GitHub’s own Importer, per the page we checked, migrates source code and commit history from external hosts into GitHub — the opposite direction — and does not enumerate exclusions on that page, so we make no claim about what it drops. Bitbucket Cloud’s import page, as fetched, describes accepting a source URL and optional credentials for a Git-based host and states nothing on that page about issues, PRs or wikis transferring — which we report as exactly that, not as proof of absence. The pattern across all three: every importer moves the git data; the richest one documents both a long list of extras and its specific losses; and nothing on any page we checked claims to move CI execution, packages, webhooks, deploy trust or SSO.
GitLab’s importer
Moves most collaboration history and documents its own losses by name: required-status-checks not imported, a roughly 30,000-comment default ceiling, custom repo roles unsupported. The honest benchmark for re-buildable with loss.
GitHub Importer
Migrates source and commit history from external hosts into GitHub. The page we checked lists what it imports and does not enumerate exclusions — a useful comparison for what a git-data-only migration covers, not an exit tool.
Bitbucket Cloud
The import page we fetched describes only accepting a Git source URL and credentials, and is silent on issues, PRs and wikis. We claim nothing beyond that page — silence is not a capability statement either way.
08 — The Decision FrameFor most teams, the honest answer is don’t.
Now the part a vendor-adjacent post would skip. Run the inventory against a typical team’s footprint and the conclusion for most of them is that a full migration is not justified today. Not because GitHub is irreplaceable, but because the one layer a new host competes on — repository hosting — is the one layer that was never the lock-in. The switching cost is the other nine rows, and nothing about a new host’s launch week changes what they cost to rebuild. A post that pretends otherwise would be selling something.
This is also exactly what the week’s news actually says. Origin’s own release describes two-way sync with GitHub as the on-ramp and keeps GitHub as the source of truth for synced repos — which makes coexistence, not migration, the realistic near-term pattern: work moves fluidly between hosts while the integration stack stays where it is. Meanwhile the direction of travel is more coupling, not less — GitHub keeps building higher-level layers on top of the repo, as we covered in GitHub’s own multi-agent platform push. Every year, the inventory above gets longer.
The decision, then, is not “migrate or stay” — it is which of four postures fits your constraints. The same logic we laid out in our build-vs-buy decision framework applies: switching costs are a fact to be priced, not an argument to be won. And because vendor lock-in is a procurement question as much as a technical one, the inventory belongs in your vendor-management file, not just your engineering backlog. When clients ask us to evaluate a move like this, our AI transformation engagements start with precisely this kind of layer-by-layer audit — because the audit usually ends the conversation cheaper than the migration would have.
No mandate, no migration
If no contract, compliance or sovereignty requirement forces a move, the inventory is your answer: one portable layer does not justify rebuilding nine. Bank this audit, revisit when constraints change.
Coexist via two-way sync
Origin’s release model — GitHub stays source of truth, changes sync both directions — lets teams adopt a new surface without touching CI, deploy trust or governance. You get the new workflow; the integration stack stays put.
Migrate deliberately, inventory-first
When a move is genuinely required, scope it against all ten rows before committing dates. The rows marked never-leaves — secrets, SSO — need your own records and IdP work no importer will do for you.
Buy optionality cheaply
Portability is cheapest at design time: keep pipeline logic in scripts your CI YAML merely calls, vendor critical dependencies, and document every trust relationship and webhook as you create it.
One projection worth making explicitly: the interesting competition over the next year is unlikely to be host-versus-host migration at all. If two-way sync becomes a normal capability — and a major vendor just shipped it as the on-ramp — the pressure on GitHub comes from coexistence eroding exclusivity, not from exits. Teams will keep the GitHub integration stack and grow new workflows beside it, and the “should we migrate” question quietly becomes “which host earns which workflow.” That is a better question, and the inventory above is how you answer it per workflow instead of per slogan.
09 — ConclusionPrice the wiring, not the repo.
Git history is the easy part — and everyone selling a migration knows it.
Of the ten layers in a typical GitHub footprint, exactly one — the git data itself — moves with a clone, function included. Workflow YAML and CODEOWNERS travel as files and leave their meaning behind. Everything else is either rebuilt by hand on the destination or reconstructed from zero on systems that were never part of the repository: runner fleets, package permissions, OIDC trust on the cloud-provider side, branch rulesets, issue history, app installations, webhooks, secrets, SSO.
The evidence for that split is not our opinion — it is what the vendors themselves document. GitHub’s docs state which layers live outside version control. GitLab’s importer, the richest of the three we checked, names its own losses. And the newest entrant’s launch copy keeps GitHub as the source of truth rather than asking anyone to leave — the clearest signal available that the people closest to the problem read the inventory the same way we do.
So price the wiring, not the repo. For most teams that pricing ends the migration conversation, and coexistence through two-way sync becomes the realistic way to try a new host without paying the full bill. For the teams that genuinely must move, the same grid is the honest project plan. Either way, the repository was never the cost — and any estimate that starts from repo count is measuring the one thing that was free to begin with.