1

Git worktrees isolam o código, mas não o runtime dos seus agentes de IA

Este texto adapta uma experiência de desenvolvimento que documentamos no The Prompt Digest, projeto que mantenho.

Imagine três agentes trabalhando no mesmo repositório. O primeiro muda o frontend, o segundo altera uma rota da API e o terceiro prepara uma migration. Cada um tem sua branch e seu Git worktree. Os arquivos deixaram de se atropelar.

Agora o agente do frontend abre a aplicação, percorre a tela e encontra tudo funcionando. Só que a URL configurada aponta para uma API iniciada a partir de outra branch. O teste passou, mas não necessariamente contra a implementação que deveria validar.

Esse é o problema: separar o diretório de trabalho não separa automaticamente os processos e o estado usados pelos testes.

Worktree A → frontend ─┐
Worktree B → API ──────┼→ API, PostgreSQL, storage e fila compartilhados
Worktree C → migration ┘

O desenho representa um cenário possível, não uma topologia obrigatória. Mesmo com código separado, os agentes podem continuar apontando para os mesmos serviços e portas. Uma migration pode alterar o banco de outra tarefa. Um worker adicional pode disputar jobs com o que já estava rodando.

Um resultado verde só responde à pergunta que o teste realmente fez. Se o navegador usou a API errada, ele não validou a integração pretendida. Se a migration usou um banco alterado por outro agente, talvez o resultado dependa de um estado que ninguém controlou.

Quatro perguntas antes de confiar no teste

Ajuda separar a análise em quatro dimensões:

DimensãoPergunta
CODEQual worktree contém a mudança que está sendo validada?
PROCESSQual processo está atendendo a requisição, e de onde ele foi iniciado?
DATAQual banco, estado de teste e histórico de migrations esse processo usa?
INTEGRATIONO conjunto continua funcionando depois que as mudanças são combinadas?

São verificações relacionadas, mas uma não substitui a outra. Abrir o frontend certo não identifica a API. Identificar a API não comprova que o banco é adequado. Duas branches aprovadas separadamente não comprovam que o merge funciona.

Por isso, a decisão sobre compartilhar um serviço precisa considerar a tarefa concreta: qual contrato ela exige, o que modifica e que efeitos pode produzir nas outras tarefas.

Isolar o necessário, sem duplicar toda a stack

Criar outra API, outro PostgreSQL, outro storage e outro worker para cada worktree resolveria parte do compartilhamento, mas também criaria custo de preparação e consumo de recursos. Nem toda mudança exige tudo isso.

No fluxo documentado, a abordagem é descobrir os serviços disponíveis, verificar se podem atender à tarefa e isolar os recursos que não são seguros de compartilhar.

Mudança só no frontend. Format, lint, typecheck, testes de componente e build podem dispensar uma API em execução. Quando o QA exige navegador, o frontend precisa ser o do worktree em avaliação. A API existente pode ser reutilizada se satisfizer o contrato necessário e se os efeitos daquele teste forem seguros no ambiente compartilhado.

Mudança na API. Testes unitários e testes por injeção, como os feitos com Fastify.inject, podem validar a aplicação sem iniciar outro servidor HTTP. Isso não significa que todas as dependências do teste desaparecem. Para verificar no navegador o comportamento da rota alterada, é preciso usar a API que contém essa implementação. Uma API antiga respondendo normalmente não serve como evidência da mudança.

Migration. Aplicar uma migration experimental no banco compartilhado modifica o ambiente dos outros worktrees. Um banco controlado ou descartável permite avaliar essa alteração sem usar o estado de outra tarefa como campo de testes. A migration não exige, por si só, duplicar frontend e storage.

Worker. Outro processo pode se tornar mais um consumidor da mesma fila. Isso não implica que o mesmo job será executado duas vezes: depende das garantias da fila. Mas pode fazer um worker de outra branch assumir um job do cenário em teste. Quando a feature depende de execução assíncrona, é preciso controlar tanto os dados quanto os consumidores. No fluxo relatado, workers são opt-in no desenvolvimento.

Compartilhar PostgreSQL em um teste somente leitura pode ser aceitável quando schema, dados e efeitos são compatíveis. Em um teste que altera dados importantes, essa mesma decisão precisa ser revista. A unidade de isolamento é o risco da tarefa, não apenas o número de worktrees.

Um registry pequeno para saber o que está rodando

Para ajudar nessa descoberta, o fluxo usa um registro local no diretório Git comum aos worktrees vinculados. Assim, ele não pertence exclusivamente ao diretório de uma feature.

Uma entrada pode reunir serviço, worktree, branch, SHA no cadastro, PID, início do processo, porta, URL e identificação declarada do banco e das migrations. Por exemplo, este recorte é ilustrativo, não o schema completo do arquivo:

{
  "service": "api",
  "worktree": "/worktrees/feature-b",
  "branch": "feature/b",
  "sha": "abc123",
  "pid": 5678,
  "port": 4402,
  "url": "http://127.0.0.1:4402",
  "database": {
    "identifier": "local-development",
    "migrationState": "unknown"
  }
}

No cadastro de uma entrada com porta, a ferramenta verifica o processo e a relação com o listener. As atualizações do registro são protegidas contra escritas concorrentes e feitas de forma atômica, para evitar um arquivo parcialmente escrito.

Isso ajuda a identificar os runtimes locais. Não transforma o arquivo em um orquestrador nem em uma prova de compatibilidade.

Uma API que responde HTTP 200 pode não implementar o contrato esperado pelo novo frontend. O banco declarado pode ter schema ou dados inadequados. O SHA registrado pode não refletir mudanças posteriores na árvore de trabalho — e editar arquivos não prova que o processo carregou essas mudanças.

A descoberta do runtime e a verificação do contrato continuam sendo etapas diferentes. O fluxo de decisão pode ser representado assim:

Descobrir runtime candidato
        ↓
Verificar identidade e liveness
        ↓
Verificar contrato, banco e efeitos exigidos pela tarefa
        ↓
Reutilizar se adequado; isolar o que não for seguro

Esse desenho descreve uma decisão operacional. A checagem de compatibilidade semântica não está automatizada pelo registry.

Porta livre ainda não é porta reservada

Outro limite aparece quando dois agentes escolhem a mesma porta:

A verifica 4402 → livre
B verifica 4402 → livre
A inicia a API  → bind em 4402 funciona
B inicia a API  → bind em 4402 falha

A sondagem observou disponibilidade em um instante. Ela não reservou o socket até o processo começar.

No fluxo descrito, a escolha das portas ainda é operacional: verificar, iniciar o processo, confirmar o listener e cadastrar. Não há alocação automática que elimine essa corrida. Um registro protegido contra escrita concorrente também não resolve, por si só, a disputa pelo bind.

Escolher os testes pelo que mudou

Antes de subir serviços, vale identificar qual validação depende deles. Format, lint, typecheck e testes unitários normalmente não precisam da stack completa. Alguns testes exigem dependências específicas; isso precisa estar explícito no plano da tarefa.

Para integração, browser QA e E2E, a pergunta é quais processos e dados são necessários para o comportamento avaliado. Testar uma mudança no componente não tem as mesmas exigências que testar uma migration ou o consumo de um job.

Uma sequência possível de gates é:

format → lint → typecheck → testes unitários/de componente
→ integração, migrations e browser/E2E conforme a mudança
→ build → validação após combinar as branches

A sequência não é uma prova de que todas essas verificações rodaram. No relato original, testes de integração podem ser ignorados quando a infraestrutura necessária não está disponível. Essa ausência precisa aparecer no resultado do QA, em vez de ser interpretada como cobertura concluída.

Antes de aceitar uma evidência de integração, deve ser possível identificar qual frontend e API foram usados, qual estado de dados sustentou o teste e quais verificações ficaram de fora. Não é necessário isolar tudo; é necessário isolar o suficiente para confiar no resultado.

O último isolamento é o da integração

O trabalho nas features pode avançar em paralelo. A combinação delas exige outro controle.

No fluxo relatado, a integração acontece de maneira serial na branch release, com lock de integração e repetição dos gates relevantes depois do merge. Uma feature pode estar correta isoladamente e ainda se tornar incompatível com outra alteração quando ambas chegam à mesma árvore.

O merge final de release para main permanece sob decisão do usuário. Ele não é uma consequência automática de o agente terminar a tarefa nem de os testes individuais ficarem verdes.

O mesmo cuidado vale para a abrangência do registro: ele coordena worktrees vinculados a um repositório Git local. Não coordena automaticamente clones em máquinas diferentes. Também não provisiona bancos descartáveis para toda migration nem direciona sozinho o browser QA para o runtime correto.

Esses limites precisam fazer parte do plano de validação. Caso contrário, a infraestrutura passa a prometer mais isolamento do que realmente oferece.

Um ponto de partida para outro repositório

É possível pedir a um agente que inspecione a stack antes de propor o fluxo. Esta é uma versão condensada do prompt usado como referência, focada nas decisões que precisam ser justificadas:

Inspecione este repositório sem alterar nada.

Mapeie apps, serviços, portas, bancos, filas, workers, storage,
variáveis de ambiente, migrations e testes de integração/browser.

Classifique cada recurso como compartilhável, compartilhável sob
condições ou exigindo isolamento. Justifique pelos contratos,
estado de dados e efeitos da tarefa.

Proponha um registry local no Git common directory com worktree,
branch, SHA, serviço, PID, início do processo, porta, URL e
proveniência declarada de banco/migrations.

Separe identidade/liveness de compatibilidade. Não trate HTTP 200
como prova do contrato nem porta livre como reserva.

Simule mudança só no frontend, mudança na API, migration, dois
agentes concorrentes e sessões de browser QA. Indique o que
reutilizar, o que isolar, quais variáveis mudam e quais testes rodar.

Para testes mutáveis e migrations experimentais, prefira dados
controlados/descartáveis. Considere os consumidores de jobs.

Defina locks, ciclo de vida, limpeza, recuperação de falhas e gates
locais/pós-merge, com integração serial na branch definida pelo
projeto. Não inclua merge automático para main.

Cite os arquivos que sustentam afirmações sobre o estado atual.
Separe o que já existe, o que recomenda e a automação futura.
Informe limitações e verificações que dependem de infraestrutura.
Não implemente antes da aprovação do plano.

O prompt não substitui inspeção nem executa o plano por si só. Seu papel é impedir que a proposta comece presumindo uma stack inteira por worktree ou confundindo descoberta de processo com compatibilidade.

O teste prático do fluxo é conseguir responder às quatro perguntas: onde está o código, qual processo atende, que estado ele usa e se a combinação continua funcionando. Sem isso, mais branches e mais testes verdes podem continuar deixando o ambiente real ambíguo.

Para quem trabalha com múltiplos agentes de programação em paralelo: vocês isolam API e banco por worktree ou reutilizam serviços seletivamente? Como verificam que o teste está usando o runtime esperado?

Carregando publicação patrocinada...