At GitHub, we recently went all in on Codespaces for developing GitHub itself. If you were a VS Code user and did all your Git stuff either in VS Code or on the command line, it was great. But for the curmudgeons like me who are big fans of local Mac development tools like TextMate and Tower, it was less great, and I was initially pretty frustrated by the forced change in tooling.
But we’re in luck! I recently learned about a utility called
mutagen and it offers a great general-purpose solution that allows you to use a Codespace for its compute and the environment, but also maintain a working copy on your local machine for code editing and source control.
How to use it
At the heart of Mutagen is a lightweight agent that watches for filesystem changes on your computer and codespace, syncing them bidirectionally.
If you’ve got a repo already set up with Codespaces, you can be up and running with Mutagen with just a few steps.
First, you need to install Mutagen on your computer (this assumes you have Homebrew installed):
brew install mutagen-io/mutagen/mutagen
If you haven’t already, install
brew install gh
You’ll want to set up
gh by running
gh auth login. Once this step is completed, you should be able to ssh into your codespaces by typing
gh cs ssh. GitHub’s docs have more info.
Next, we’ll SSH into the codespace, but we’ll be passing
gh an option to also open up an SSH tunnel. This tunnel will let you ssh into
localhost at the specified port and be connected to your codespace:
gh cs ssh --server-port 1234
You’ll be prompted to pick the codespace to connect to.
You can use any available port you like. When you exit the shell it opens, the tunnel will be disabled too.
Once a connection is established,
gh will provide you with a connection string and shell.
Next, open up
~/.ssh/config and add a configuration entry for your codespace. We need to ensure that when connecting to the codespace, the
NoHostAuthenticationForLocalhost is always used (mutagen doesn’t have a way for you to specify this option to its CLI). We’ll create a config entry for a host called
codespace that will automatically use the correct port and options.
Host codespace HostName localhost Port 1234 User root NoHostAuthenticationForLocalhost yes
Swap port 1234 for whichever port you actually chose earlier.
Also, I recommend setting up a
~/.mutagen.yml file with some sensible defaults. Setting the
vcs: true option is essential here; your codespace’s
.git folder contents will be totally different from your own machine’s.
sync: defaults: ignore: vcs: true paths: - "/vendor/*" - "/tmp/*"
More on setting up ignores in the docs
Now, the fun part! Make sure that your codespace and your local working directory have the same branch and SHA checked out (this is important; more on that shortly), and set up the sync profile:
mutagen sync create --name=codespace path/to/project/folder codespace:/path/to/codespaces/working/directory
This creates a synchronization session and initiates it. You can check on the state of this session with
mutagen sync list.
Once you complete this, you will have bidirectional sync between your local working directory and the working directory on your Codespace. Having the sync be bidirectional is handy; it allows you to, for instance, run your linters while ssh’ed into your codespace and have those file changes synced right back up.
Once this sync session is created, you don’t need to create it again; if the Mutagen daemon is running it will handle disconnects and reconnects just fine. However, sync sessions are immutable so if you need to change settings, delete the sync session:
mutagen sync terminate codespace
This approach isn’t without its faults. This is a fairly tightly coupled arrangement and it is a bit more fiddly than a purely local dev environment. Because codespaces come with their own Git working directory, you do have to take special care to make sure both working directories are on the same branch/SHA before you start your tunnel, and then you’ll need to reset your working directory on the Codespace after you disconnect the tunnel (or vice versa if you plan to perform your Git operations on the Codespace).
But I’m really quite fond of it; for a little bit of extra fiddling around, I get to fully enjoy codespaces, but I retain full flexibility and freedom to use the local tools I enjoy. I can keep using Tower and GitHub Desktop to write my commits or do rebases. I can use Kaleidoscope to make sense of complex diffs and resolve merge conflicts. And I can keep using my beloved TextMate until someone pries it out of my cold dead hands.