# Uma ordem, um worktree: isolando agentes de código com git worktree

> O jeito mais barato de reduzir o raio de explosão de um agente é não deixá-lo tocar no seu checkout. Os detalhes que fazem isso ser seguro de verdade.

- Por: Djan Magno
- Publicado em: 2026-09-19
- Atualizado em: 2026-09-19
- URL: https://t25.io/blog/uma-ordem-um-worktree-isolando-agentes-de-codigo/
- Engenharia · git worktree, agentes de código, claude code, codex, isolamento, segurança, paralelismo

> **Resumo:** > - **`git worktree`** dá a cada tarefa um diretório e um branch próprios, compartilhando o mesmo repositório. É o isolamento mais barato para rodar agentes de código em paralelo.
> - No T25, cada tarefa ganha um worktree em `.factory/worktrees/<projeto>/<TASK-ID>` num branch como `feat/TASK-0042`. Nenhum agente de implementação roda no checkout principal.
> - Criar o worktree é fácil. O trabalho está nos guards: caminho dentro da raiz, dono registrado, lock por tarefa, base sincronizada com `origin` e limpeza que se recusa a apagar trabalho.

## Por que isolar agentes de código

Um agente de código trabalhando no mesmo diretório que você cria três problemas, e todos pioram quando há mais de um agente:

- **Colisão.** Dois agentes editando o mesmo checkout sobrescrevem o trabalho um do outro, e o `git status` vira uma mistura que ninguém sabe separar.
- **Contaminação.** Você está no meio de uma mudança, o agente roda os testes, falha por causa do seu código incompleto e "corrige" o que não era dele.
- **Raio de explosão.** Um `git checkout .` ou um `rm` mal pensado do agente atinge o seu trabalho não commitado.

Um container resolve os três, mas custa imagem, volume e credenciais montadas. Um clone separado resolve também, mas duplica o `.git` e perde o compartilhamento de objetos. O `git worktree` fica no meio: diretórios de trabalho independentes, um único repositório por trás.

## O básico em um comando

```bash
git worktree add -b feat/TASK-0042 .factory/worktrees/app/TASK-0042 main
```

Isso cria o diretório, cria o branch a partir da `main` e faz o checkout nele. O agente recebe esse diretório como `cwd` e só isso. Quando a tarefa termina e o trabalho está commitado, `git worktree remove` tira o diretório.

Se fosse só isso, este post acabaria aqui. O resto é o que dá errado quando uma fábrica cria centenas desses automaticamente.

## Os guards que fazem isso ser seguro

O `WorkspaceManager` do T25 é tratado como código sensível. Cada item abaixo existe porque o caso sem ele é plausível.

### 1. Git sem shell

Todas as chamadas de git passam por `execFile` com uma lista de argumentos, nunca por uma string de shell. O ID da tarefa e o nome do branch nunca são interpolados num comando, então um título de tarefa com `; rm -rf` é só texto. O processo também tem timeout duro e é morto com `SIGKILL`, para que um git travado não deixe o repositório num estado intermediário enquanto alguém tenta limpar.

### 2. ID e branch validados

O ID da tarefa precisa casar com `^[A-Za-z0-9][A-Za-z0-9._-]*$` antes de virar parte de um caminho. O branch base passa por `git check-ref-format --branch`. Nada que tenha `..` ou barra entra no caminho.

### 3. O caminho tem que ficar dentro da raiz

Antes de usar qualquer caminho de worktree, ele é resolvido com `realpath` e comparado com a raiz configurada. Se o caminho relativo começa com `..`, é absoluto ou está vazio, a operação falha. Isso pega o caso do symlink: um diretório que parece estar dentro da raiz mas aponta para fora.

### 4. Dono registrado, e registrado duas vezes

Um diretório existir no lugar certo não prova que a fábrica o criou. Antes de reusar um worktree, o T25 exige duas coisas:

- um arquivo de metadados (`.factory-metadata/<TASK-ID>.json`) com o ID da tarefa, o branch e o caminho canônico, e os três precisam bater;
- o caminho precisa aparecer em `git worktree list --porcelain` com o mesmo branch.

Sem as duas, a fábrica se recusa a adotar o diretório. Isso impede que ela "herde" uma pasta que alguém criou à mão ou que sobrou de outra ferramenta.

### 5. Lock por tarefa

Dois processos preparando o mesmo worktree ao mesmo tempo é uma corrida clássica. O lock é um arquivo criado com a flag `wx` (falha se já existe) contendo o PID de quem pegou. Se o lock existe e o processo dono está vivo, a segunda tentativa falha com a mensagem certa. Se o processo morreu, o lock é considerado velho e é assumido.

### 6. Base sincronizada antes de ramificar

Este guard veio de um erro real. Numa rodada de dogfood, uma tarefa nasceu de uma `main` local atrasada em relação ao `origin`: um pull request tinha sido mesclado direto no GitHub, fora da fábrica. O resultado foram três ciclos de QA e implementação perseguindo uma asserção de teste que já estava corrigida no remoto.

Agora, antes de criar um worktree novo, o T25 faz `git fetch origin main` seguido de `git merge --ff-only origin/main`. O `--ff-only` é a parte importante: se o clone local divergiu por qualquer motivo, a operação falha alto em vez de seguir com base errada.

### 7. Limpeza que não apaga trabalho

`cleanup()` confere o dono de novo, roda `git status --porcelain --untracked-files=all` e se recusa a continuar se houver qualquer mudança, incluindo arquivo não rastreado. Nunca usa `--force`. Perder o trabalho de um agente por uma limpeza automática é pior do que deixar um diretório sobrando.

## Nome de branch que passa no seu repositório

O branch segue o prefixo do tipo da tarefa, no padrão de Conventional Commits: `feat/`, `fix/`, `refactor/`, `docs/`, `chore/`, seguido do ID. A escolha é prática: muitos repositórios têm hooks de pre-push ou regras de proteção que recusam nomes fora do padrão, e um branch `factory/...` morreria no push.

## Worktree resolve tudo?

Não. Worktree isola o **sistema de arquivos do repositório**, não o processo. O agente ainda roda com o seu usuário, enxerga o resto da máquina e usa as credenciais do CLI dele. Por isso o T25 empilha outra camada por risco: tarefas de risco médio para cima podem rodar o agente dentro de um sandbox Docker, com uma política assinada que o worker valida antes de executar. Restringir a rede desse sandbox de verdade ainda está na nossa lista. Worktree é a primeira camada, não a única.

| Opção | Isola arquivos do repo | Isola processo | Custo de setup | Compartilha objetos git |
|---|---|---|---|---|
| Checkout principal | Não | Não | Nenhum | Sim |
| `git worktree` | Sim | Não | Baixo | Sim |
| Clone separado | Sim | Não | Médio | Não |
| Container + volume | Sim | Sim | Alto | Depende |

## Checklist para fazer o mesmo

Se você está montando isolamento para os seus agentes:

1. Um worktree e um branch por tarefa, criados a partir de uma base sincronizada com `--ff-only`.
2. Git chamado sem shell, com timeout e `SIGKILL`.
3. ID de tarefa validado por regex antes de virar caminho; `realpath` e checagem de raiz em todo uso.
4. Dono gravado em metadados e confirmado em `git worktree list`.
5. Lock por tarefa com PID e detecção de lock velho.
6. Limpeza que recusa diretório sujo e nunca força.

## Perguntas frequentes

### Posso rodar vários agentes em paralelo no mesmo repositório?

Sim, desde que cada um tenha o próprio worktree. Eles compartilham o banco de objetos do git, mas cada um tem diretório de trabalho, índice e branch próprios.

### O worktree é apagado quando a tarefa termina?

Só se estiver limpo. A limpeza do T25 se recusa a remover um worktree com mudanças não commitadas ou arquivos não rastreados, e nunca usa `--force`.

### Por que não usar um container para tudo?

Container isola mais, mas custa imagem, volume e credenciais montadas em cada tarefa. Worktree resolve colisão e contaminação com custo quase zero. O T25 usa os dois em camadas: worktree sempre, sandbox Docker a partir do risco médio.

### Onde ficam os worktrees do T25?

Numa pasta local e ignorada pelo git, por padrão dentro de `.factory/worktrees/`, separada por projeto. Nada disso é commitado.
