# turbolift: making the same change across many GitHub repositories

> turbolift is a Go command line tool from Skyscanner that automates the mechanical parts of a cross-repository change, cloning every target, applying your own edits, committing them and raising a pull request for each. It keeps the edits manual and interactive, and it supersedes an earlier internal batch system called Codelift.

**Skyscanner/turbolift** — A simple tool to help apply changes across many GitHub repositories simultaneously

- Repository: https://github.com/Skyscanner/turbolift
- Stars: 531 · Forks: 42
- Language: Go
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/skyscanner-turbolift

## The philosophy is to automate the boring parts only

turbolift is described as a simple tool to help apply changes across many GitHub repositories simultaneously, and the README explains the design in terms of what it refuses to do. Anyone who has had to manually make changes to many GitHub repositories knows that it is hard to beat the simplicity of just cloning the repositories and updating them locally. You can use any tools necessary to make the change, and there is a degree of immediacy in having local files to inspect, tweak or run validation. The tool's own summary of that approach is blunt, that it is dumb but it works, and that it does not scale well. Manually cloning and raising pull requests against tens or hundreds of repositories is painful and boring. So turbolift essentially automates the boring parts and stays out of the way when it comes to actually making the changes. It automates cloning, committing, and raising pull requests en masse, so that you can focus on the substance of the change. That division is the whole design, and it is why the tool is a Go binary with a small dependency set rather than a scripting framework with a plugin system.

## Why it replaced a centralised batch system

The README carries a historical note that explains a good deal about the shape of the tool. Turbolift supersedes an internal system at Skyscanner named Codelift, which was a centralised batch system requiring changes to be scripted upfront and run overnight. While Codelift was useful, the team found that a decentralised, interactive tool is far easier and quicker for people to use in practice. The contrast between the two is worth spelling out, because it explains the command structure. A batch system asks you to describe the change as a program before you have made it, and it runs later without you. An interactive tool asks you to make the change and then does the repetitive delivery. The overnight run is also where the review load lands at once, which is one of the caveats the README later warns about. A blog post is linked as giving a longer background for the thinking behind turbolift, and the repository carries an AGENTS.md, a CODE_OF_CONDUCT.md, a CONTRIBUTING.md and a .mise.toml, so the project is set up for contributors as well as users. It is Apache-2.0 licensed, the primary language is Go, and the release history shows 4.1.3 from 2025-11-13, 4.1.2 from 2025-11-03 and 4.1.1 from 2024-12-12, with the last push to the repository on 2026-08-01.

## Installing it, and the GitHub CLI dependency

Installation is one command if you use Homebrew, which is the recommended route.

```bash
brew install skyscanner/tools/turbolift
```

The GitHub CLI, gh, is a dependency of turbolift and will be installed automatically in that case. The alternative is to download a pre-built binary archive from the Releases page, extract it and move it onto your path, and you must then install the GitHub CLI yourself with brew install gh. There is a wrinkle on macOS that the README spells out, since the binaries are not currently notarized for MacOS Gatekeeper, and if errors are displayed you can use the xattr command to un-quarantine the binary or right-click on it in Finder and choose Open once to allow future execution, with distribution to be improved under a tracked issue. Before using turbolift at all, run gh auth login once and follow the prompts, to authenticate against github.com and your GitHub Enterprise server. If you are working with repositories on a GitHub Enterprise server, the environment variable GH_HOST has to be set to point at that server. None of this is exotic, but it does mean the tool is not a single self-contained binary that works immediately after download.

## Six phases, run in series

Making changes with turbolift is split into six main phases, and the README says it is expected that you will go through these phases in series. The first is init, getting set up. The second is identifying the repositories to operate upon. The third is running a mass clone of the repositories. The fourth is making changes to every repository. The fifth is committing changes to every repository. The sixth is creating a pull request for every repository. The turbolift tool automates as much of the Git and GitHub heavy lifting as possible, but leaves you to use whichever tools are appropriate for making the actual changes. The phase structure is what makes the tool safe to use, because each phase is a checkpoint where you can look at what has happened before continuing. The init phase creates a campaign directory that holds settings and working copies of repositories, and the campaign name is used as the branch name for any changes that are created.

the command turbolift init with a --name flag set to the campaign name

You then move into that directory.

You then change into that directory with cd and the campaign name.

The campaign concept is the state that makes the later phases possible, since the branch name, the repository list and the working copies all live together, and a campaign is what you point the later commands at.

## Finding the repositories and cloning them

The repositories to change are listed in repos.txt, which you update either manually or with a tool that identifies them. The README recommends gh-search as an excellent tool for performing GitHub code searches, since it can output a list of repositories in a format that turbolift understands.

```
$ gh-search --repos-with-matches YOUR_GITHUB_CODE_SEARCH_QUERY > repos.txt
```

This is the step that turns a code search into a batch operation, and it is worth noting that the search is over code rather than over repository metadata, so the natural workflow is to find every repository containing a particular pattern and then change that pattern everywhere it appears. Where you need different files for different subsets of repositories, the default repo file is repos.txt but you can override this on any command with the --repos flag.

```bash
turbolift foreach --repos repoFile1.txt -- sed 's/pattern1/replacement1/g'
turbolift foreach --repos repoFile2.txt -- sed 's/pattern2/replacement2/g'
```

The clone phase then clones all repositories listed in repos.txt into the work directory. By default the cloning policy is to create a branch on the target repository, and if you do not have permissions to push a branch on the target repository, turbolift will fork it instead. If you want to fork all the repositories rather than letting turbolift decide for you, there is a --fork flag. The usage is simply the command on its own.

```bash
turbolift clone
```

The default branch, push-or-fork based on your permissions, is a small design decision with real consequences, since it means the tool behaves correctly for a contributor who lacks write access and for a maintainer who has it, without the user having to say which case applies.

## Making the change, and running one command across all of them

Once cloned, the work happens in the checked-out repositories under the work directory, and the README is emphatic about the freedom you have. You can do this manually using an editor, using sed and similar commands, or using codemod or comby. The point is underlined in the source text: you are free to use any tools that help get the job done. If you wish to, you can run the same command against every repo using the foreach subcommand with the command you want to run after a double dash. The README also mentions turbolift foreach as the way to run automated tests locally for each repository, which appears again in the caveats section as a recommended practice rather than an afterthought. This is the phase where the tool is deliberately passive, and it is the phase where the quality of the result is decided, since turbolift will happily commit and publish whatever is in the working tree.

## The caveats are the operational guidance

The caveats section opens with the line that this is a tool with great power and with it great responsibility, and the guidance under it is more useful than most of the documentation elsewhere in the project. Do not use turbolift to raise pointless pull requests, and if a reviewer might think the change is trivial or unimportant, think about whether it is actually needed. If you need to make a change to a large number of repositories, the team's experience is that it is generally better to raise pull requests to a small subset at first and collect feedback, and the mechanism for that is simply to comment out repositories in repos.txt so turbolift temporarily ignores them. For complicated or potentially contentious changes, think of ways to validate them before raising pull requests, which could range from working in a pair, through writing a peer-reviewed script, to preparing a design document. If you can run automated tests locally, do it. Raising draft pull requests can be a good way to collect feedback, especially CI test results, with less pressure on reviewers, and the flag for that is --draft. And in an organisation with shared infrastructure such as CI, raising many pull requests in a short timeframe can cause a lot of load, so consider spacing out pull request creation using the --sleep option or by commenting out chunks of repositories. The design implication is that the tool offers deliberately coarse controls, a whole-file exclusion list and a delay, rather than fine-grained scheduling, which matches the interactive philosophy.

## What the implementation looks like

The repository is a conventional Go project and the module manifest shows exactly what it is built on. The module path is github.com/skyscanner/turbolift and it requires Go 1.23. The direct dependencies are small and each has an obvious job. spf13/cobra provides the command structure with the subcommands for init, clone, commit, create-prs and foreach. fatih/color handles the terminal output colouring, and briandowns/spinner provides the progress spinner that makes a mass clone feel responsive. manifoldco/promptui is the interactive prompt library, which is what turns the commit phase into a place where you can edit a message before it goes out. rodaine/table renders the tabular summary of results, and alessio/shellescape is the helper for quoting arguments passed to external commands, which is the detail that makes the foreach subcommand safe to use with arbitrary shell commands and filenames. stretchr/testify provides the test assertions. The top level of the tree shows cmd/ for the command definitions, internal/ for the implementation, docs/ for the documentation, main.go at the root, and a .goreleaser.yaml for the release builds that produce the binary archives on the Releases page. The release cadence of patch versions on a few weeks apart suggests a tool that is used in anger and fixed as it breaks.

## Conclusion

Adopt turbolift if you have to apply the same change to dozens of GitHub repositories and want the pull requests raised for you while keeping the edit itself under your control. Skip it if the change touches one or two repositories, since the setup is not worth it. Verify first that the GitHub CLI is installed and authenticated, that GH_HOST is set if the repositories live on a GitHub Enterprise server, and that you have considered the review load your batch of pull requests will create.

## FAQ

### What does turbolift automate and what does it leave to you?

It automates cloning, committing and raising pull requests across many repositories at once, and it leaves the actual edit to you. You make changes in the checked-out repositories under the work directory using any tools you like, including an editor, sed, codemod or comby, and turbolift handles the delivery. The work is split into six phases run in series, from init through identifying repositories, cloning, editing, committing and creating pull requests.

### How do I install turbolift and what does it depend on?

The recommended route is brew install skyscanner/tools/turbolift, which also installs the GitHub CLI dependency. Otherwise you can download a pre-built archive from the Releases page and put it on your path, and install the GitHub CLI with brew install gh. You must run gh auth login once first, and set the GH_HOST environment variable if the repositories live on a GitHub Enterprise server.

### What guidance does the turbolift README give for large scale changes?

It advises against pointless pull requests, and suggests raising pull requests to a small subset first to collect feedback, which you do by commenting out repositories in repos.txt so turbolift ignores them. For contentious changes it suggests validating first, running automated tests locally with turbolift foreach, raising draft pull requests with --draft to reduce pressure on reviewers, and spacing out pull request creation with --sleep to avoid loading shared CI infrastructure.

## Sources

- [Issues](https://github.com/Skyscanner/turbolift/issues)
- [License: Apache-2.0](https://github.com/Skyscanner/turbolift/blob/main/LICENSE)
- [README](https://github.com/Skyscanner/turbolift/blob/main/README.md)
- [Releases](https://github.com/Skyscanner/turbolift/releases)
- [Skyscanner/turbolift on GitHub](https://github.com/Skyscanner/turbolift)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/skyscanner-turbolift
