O gargalo do checkout único

Estou desenvolvendo um app de barra de menu no macOS. Tenho três features no backlog: um sparkline de consumo, notificações nativas, e um widget de desktop. As três são independentes. As três vou fazer com Claude Code.

O problema: Claude Code trabalha em um diretório. Um diretório tem uma branch. E git checkout é como uma rotatória de uma pista só: só passa um.

Se quero avançar as três ao mesmo tempo, minhas opções clássicas são:

  1. Ping-pong de stash: git stash, mudar branch, trabalhar, git stash pop, rezar para não ter conflitos. Repetir até a loucura ou aposentadoria, o que chegar primeiro.

  2. Clonar o repo três vezes: Funciona, mas agora tenho três cópias de .git/, três históricos independentes, e um git fetch para fazer em cada um. Um desperdício.

  3. Aceitar a vida serial: Uma feature depois da outra. Seguro, previsível, e lento como um merge sort manual.

Nenhuma é legal. Mas tem uma quarta opção que está no git desde 2015 e que quase ninguém usa.

Worktrees: a solução que você já tinha instalada

Um worktree é um segundo diretório de trabalho que compartilha o mesmo repositório .git. Sem cópias, sem clones, sem magia negra.

A analogia: seu repo é uma biblioteca. Até agora você tinha uma mesa onde só podia ter um livro aberto. Um worktree é colocar mais mesas. Cada uma com um livro diferente aberto, mas todas pegando da mesma estante.

~/code/meuapp/                   ← mesa 1 (main)
     .git/                       ← a biblioteca (uma só)

~/code/meuapp-sparkline/         ← mesa 2 (feature/sparkline)
     .git  ← arquivo, não pasta (ponteiro para a biblioteca)

~/code/meuapp-notificacoes/      ← mesa 3 (feature/notifications)
     .git  ← outro ponteiro

Cada diretório é um checkout completo com todos os arquivos. Você pode compilar em um, rodar testes em outro, e ter seu agente de IA trabalhando no terceiro. Ao mesmo tempo.

Criar é uma linha só

Do seu repo principal:

git worktree add ../meuapp-sparkline -b feature/sparkline
git worktree add ../meuapp-notificacoes -b feature/notifications

Pronto. Dois diretórios novos, cada um na sua branch, compartilhando toda a base de dados git. Nada de clonar, nada de configurar remotes, nada de duplicar histórico.

O que compartilham e o que não

Isso é importante. Os worktrees compartilham todo o repo: commits, branches, tags, remotes, hooks, configuração. Se você fizer um commit no worktree do sparkline, pode ver imediatamente no das notificações sem fazer fetch nem nada, porque é a mesma base de dados.

O que não compartilham:

  • Os arquivos em disco (cada mesa tem sua cópia de trabalho)
  • A staging area (cada um tem seu próprio git add)
  • O HEAD (cada um aponta para sua branch)

Em outras palavras: o estado de “no que estou trabalhando” é privado de cada worktree. Todo o resto é comum.

O fluxo de trabalho com agentes de codificação

Aqui é onde fica interessante. Com worktrees, você pode ter literalmente vários agentes trabalhando em paralelo sobre o mesmo projeto:

# Terminal 1: Claude Code no sparkline
cd ~/code/meuapp-sparkline
claude

# Terminal 2: Claude Code nas notificações
cd ~/code/meuapp-notificacoes
claude

# Terminal 3: main intacto, o app rodando
cd ~/code/meuapp
make run

Cada instância do Claude tem seu próprio diretório, sua própria branch, seu próprio .build/. Não se atropelam. Não competem pelo index. Não precisam fazer stash de nada.

E como compartilham a base de dados git, quando um dos agentes termina e faz push, os outros já veem essa branch.

Fazer merge: exatamente igual ao normal

Os worktrees não mudam nada do fluxo de merge. São branches normais em diretórios separados:

# Opção A: merge local
cd ~/code/meuapp
git merge feature/sparkline
git merge feature/notifications

# Opção B: PRs (o habitual)
cd ~/code/meuapp-sparkline
git push -u origin feature/sparkline
# Criar PR no GitHub/Gitea, review, merge

Quando termina, você limpa:

git worktree remove ../meuapp-sparkline
git branch -d feature/sparkline  # se já foi mergeada

As pegadinhas que ninguém te conta

1. Uma branch, um worktree

Você não pode ter main checada em dois worktrees ao mesmo tempo. É por design: evita que dois diretórios modifiquem o mesmo HEAD e se corrompam. Se precisa de um segundo checkout de main, crie uma branch temporária.

2. O primeiro build é do zero

Cada worktree tem seu próprio diretório de build. A primeira compilação vai ser lenta. Depois, cada worktree mantém seu cache independente, que é justamente a vantagem sobre o git checkout clássico (que invalida o cache toda vez que você muda de branch).

3. Arquivos locais não rastreados

Seu .env.local, configurações de editor, arquivos que não estão no git… não são copiados para o worktree novo. Você vai ter que recriá-los ou fazer symlinks.

4. Apps com estado compartilhado em disco

Se seu app escreve dados em ~/Library/Application Support/ ou similar, duas instâncias do app de worktrees distintos vão competir pelo mesmo arquivo. Não é um problema do worktree, é um problema de rodar duas instâncias do mesmo app. A solução: não rodar duas ao mesmo tempo, ou parametrizar o diretório de dados por build.

5. Não apague o diretório na mão

Se você fizer rm -rf do worktree em vez de usar git worktree remove, o git continua pensando que a branch está ocupada. Execute git worktree prune para limpar as referências órfãs.

6. O remote não sabe de nada

Os worktrees são 100% locais. Gitea, GitHub, GitLab… nenhum remote sabe que existem. Só veem git push normais com branches normais. É como perguntar se seu servidor tem problemas com você usar Vim ou VS Code: ele não sabe, não o afeta.

Boas práticas

Convenção de nomes: coloque os worktrees como irmãos do repo original, com um sufixo descritivo:

~/code/meuapp/                   ← main
~/code/meuapp-sparkline/         ← feature
~/code/meuapp-notificacoes/      ← feature
~/code/meuapp-hotfix-login/      ← hotfix

Assim um ls ~/code/meuapp* te mostra tudo de uma vez.

Um worktree por feature, não por capricho: crie worktrees para trabalho que realmente vai ser paralelo. Se você vai fazer uma coisa depois da outra, uma branch normal com checkout é suficiente.

Limpe quando terminar: os worktrees abandonados são como as branches que ninguém apaga — se acumulam e confundem. git worktree list é seu amigo.

Não edite o mesmo arquivo de dois worktrees: tecnicamente pode, cada um tem sua cópia. Mas se ambos modificarem o mesmo arquivo, você terá conflitos no merge. Tente fazer com que as features toquem áreas distintas do código.

Proposta de fluxo de trabalho completo

Para quem quer um fluxo de trabalho organizado, aqui vai o que eu uso:

# 1. Criar worktrees para as features do sprint
cd ~/code/meuapp
git worktree add ../meuapp-feat-a -b feature/feat-a
git worktree add ../meuapp-feat-b -b feature/feat-b

# 2. Lançar um agente em cada um
cd ~/code/meuapp-feat-a && claude    # terminal 1
cd ~/code/meuapp-feat-b && claude    # terminal 2

# 3. Ir fazendo merge conforme terminam
cd ~/code/meuapp-feat-a
git push -u origin feature/feat-a   # criar PR

# 4. Limpar o que já foi mergeado
git worktree remove ../meuapp-feat-a
git branch -d feature/feat-a

# 5. Ver o que sobrou vivo
git worktree list

O ciclo é: criar → trabalhar em paralelo → push/PR → merge → limpar. Cada worktree vive o que vive a feature, nem mais nem menos.

Para fechar

Os worktrees estão no git desde a versão 2.5 (julho de 2015). Mais de dez anos. E a maioria das pessoas continua fazendo git stash como se estivéssemos em 2010.

Com a chegada dos agentes de codificação, o gargalo não é mais a velocidade com que você escreve código — é a velocidade com que consegue mudar de contexto. E os worktrees eliminam essa mudança de contexto completamente: você não muda de branch, muda de diretório. cd em vez de checkout.

Que é, no fim das contas, o que sempre deveríamos ter feito.


TL;DR: git worktree add ../nome -b branch cria um segundo diretório de trabalho sobre o mesmo repo. Sem cópias, sem stash, sem invalidar caches. Perfeito para ter vários agentes de codificação trabalhando em paralelo. Limpe com git worktree remove quando terminar.

Este artigo foi escrito originalmente em espanhol e traduzido com a ajuda de IA.