Skip to main content
Hat Boy Software

What breaks when you move a GitHub repo

Moving repositories between GitHub organizations without breaking CI/CD: the hidden dependencies, and the order to change them in.

Transferring a repository to another GitHub organization looks like a single button, and for history, issues and pull requests it is. What breaks is everything outside the repository that refers to it by owner and name: cloud logins, tokens, infrastructure code and deployment controllers, and any one of them can stop deploys on transfer day. The fix is to find those references first and change them in the right order.

We are planning a move like this now, for two repositories, one with live CI/CD into several environments. The transfer has not happened yet, so this is the plan and its reasoning, not a finished result. It came from a read-only scan of the repository, which is the first step we would recommend to anyone.

What GitHub carries over, and what it does not

GitHub’s documentation says a transfer keeps git history, issues, pull requests and the wiki, keeps webhooks, secrets and deploy keys with the repository, and redirects the old URL. That makes the move feel safe.

It is less explicit about environments, variables and repository-level self-hosted runners, so inventory them and confirm each after the move. Some things cannot come along because they belong to the source organization: organization rulesets, organization secrets and variables, organization runners, and GitHub App installations. Every external trust relationship written in terms of the old owner also stops matching.

The hidden dependencies

DependencySymptom after the moveFix
OIDC federated credentials in your cloudEvery cloud login step in CI failsAdd credentials for the new subject before the move; remove old ones after verification
Access tokens owned by the old organizationRunner management, infrastructure applies and API calls failReplace with a GitHub App installed on the new organization
Terraform or OpenTofu GitHub provider with a hard-coded ownerPlan wants to create or replace environments, variables and secretsMake the owner a variable; read the plan; reconcile state instead of replacing
Argo CD repoURL pointing at the old pathWorks through the redirect, until it doesn’tUpdate repoURL and repository credentials explicitly
Hard-coded owner/repo in CI scripts and testsScripts call the API at the old pathDerive from GITHUB_REPOSITORY or one variable
Self-hosted runnersJobs queue with no runnerConfirm after the move; re-register if they did not follow
Organization rulesets, secrets, variables and AppsProtections and integrations silently absentInventory beforehand; re-create or re-install

OIDC subjects: add first, remove last

Workload identity federation lets GitHub Actions sign in to a cloud with no stored secret. But the cloud trusts a specific subject (sub) claim in the token GitHub issues, and that claim names the owner. Older repositories use a name-only form:

repo:<owner>/<repo>:environment:<env>

Since July 15, 2026, GitHub uses an immutable format for repositories that are created, renamed or transferred, adding numeric IDs: repo:<owner>@<owner-id>/<repo>@<repo-id>:environment:<env>. So a transfer now also switches the subject format, and a credential written for repo:<new-owner>/<repo>:... will not match.

The safe sequence is to add a federated credential for the exact subject the moved repository will present, in every environment, before the transfer. Build it from the new organization’s ID and the repository’s ID, and confirm it with a test repository rather than assuming. Once the moved repository has signed in everywhere, delete the old credentials. If identities live in infrastructure code, make the subject a variable.

Check every cloud: a second provider with its own federation fails the same way.

Tokens: replace them with a GitHub App

Workflows that manage runners, environments or secrets often use a token created in the old organization. After the move it either stops working or, worse, keeps working, leaving an old-organization credential, possibly with reach into its other repositories, inside the new one.

Create the replacement before the move. A GitHub App installed on the new organization beats a personal token: it is not tied to a person, its permissions can be limited to what the workflows use, and it can be installed on only the repositories that need it. Stage it as a new secret, switch over on the day, then revoke the old token.

Infrastructure code: read the plan before you trust it

If the repository manages its own GitHub settings with Terraform or OpenTofu, the provider’s owner is often a literal, set as owner or through the GITHUB_OWNER environment variable. Check both: the provider’s current documentation says GITHUB_OWNER takes precedence. Point it at an organization where the repository does not exist yet, and the provider cannot find the environments and secrets it manages and plans to create them.

Make the owner one variable. A plan with the new owner before the transfer will show these resources as missing, so treat it as a checklist, not a verdict. After the transfer, read every github_* change. Where the plan still wants to replace something that exists, reconcile state instead of applying:

# re-point state at the environment as it exists after the move
tofu state rm 'github_repository_environment.this["prd"]'
tofu import  'github_repository_environment.this["prd"]' '<repo>:prd'

The environment import ID is <repo>:<environment>; the provider supplies the owner. The goal is a drift run with no GitHub replacements.

Argo CD: don’t rely on the redirect

Argo CD will keep syncing from the old URL through the redirect. That is fragile for two reasons. The redirect is permanently deleted if anyone creates a repository with the old name at the old owner. And Argo CD picks credentials by URL (an exact match for a repository entry, the longest matching prefix for a credential template), so a credential set up for the new organization’s URL will not be used for an application still pointing at the old one. Update repoURL in each application, update the credential, and sync a non-production environment first.

Hand over artifacts, not source access

Sometimes a moved repository builds something from source owned by the old organization. Keeping that build leaves a credential to the old organization’s source inside the new one. The cleaner pattern is to hand over signed artifacts: the source signs images in CI with keyless cosign and grants a pull-only registry token with a deliberate expiry, and the receiver pulls by digest and verifies the signer before deploying:

cosign verify \
  --certificate-identity "https://github.com/<owner>/<repo>/.github/workflows/<sign>.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  <registry>/<image>@sha256:<digest>

Check the identity against a real signed image first; a small difference in workflow path or ref fails verification.

Transfer day and verification

The plan for the day: freeze merges and pause merge or review automation. Transfer, switch workflows to the new credential, set the owner variable, update Argo CD, re-create organization-level items, and confirm the runners. Then verify with real work: a trivial pull request runs CI green, a non-production deploy succeeds, an infrastructure drift run is clean, and the new organization holds no credential to anything it should not reach. Only then remove the old OIDC credentials and revoke the old token.

How we can help

We plan and run platform moves like this one, starting with a read-only scan for every place the platform names itself. See our services for how we scope platform reliability work.

Share this article

Related articles

Platform Reliability & FinOps

Upgrading Hugo from 0.105 to 0.167: what broke

How we moved a Hugo site across 62 minor versions: the trial build, one hard error, three deprecations, two silent breaks, and the URL diff.

Platform Reliability & FinOps

Your CI watchdog can't live on the pool it watches

A self-hosted runner pool was down for 6 hours 38 minutes. Its watchdog ran on the same pool. What went wrong, and how to monitor CI from outside.

← All articles

Not sure where to start? Start with an assessment.

A senior review of your app, cloud estate, or AI platform. Scoped and quoted before work starts. It ends in a prioritized plan, so you decide what to fix and when.

Talk to an engineer