Fluxo de PR com Worktree Isolado
Procedimento para enviar uma alteração como pull request a partir de um worktree git temporário cortado do branch padrão do origin, quando o checkout principal não deve ser tocado — porque está sujo (WIP não commitado), desatualizado em relação ao remoto, ou compartilhado com outros agentes. O fluxo cobre a criação do worktree, lacunas de dependências em worktrees novos, polling limitado de CI e proteções de limpeza conscientes do modo de merge.
Por que Isolamento de Worktree
Quando múltiplas sessões de agente entram no mesmo diretório de trabalho (por exemplo, através de lançamento em lote de sessões), o HEAD do git, o branch atual e as alterações não commitadas são estado compartilhado — cada sessão move o chão debaixo da outra. Em um incidente medido, dentro de um minuto de uma sessão criar um branch com git switch -c, uma segunda sessão no mesmo checkout ramificou a partir dele, commitou e fez merge para o branch padrão.
Padrão de detecção (confirma uma colisão em minutos):
- Sinais: arquivos que você não escreveu aparecem, ou
git statusnão corresponde mais ao snapshot que você tirou quando sua sessão começou. - Escrutínio de timestamp:
ls --time-style=full-iso(ou equivalente da sua plataforma). Vários arquivos compartilhando o mesmo milissegundo significa que uma operação git (checkout / merge / stash) os escreveu, não edições manuais ou sequenciais de LLM. git reflog --date=isoestabelece a linha do tempo: quem fez checkout, commitou ou mergeou, e quando.- Se seu ambiente de agente fornece um recurso de listagem de sessões, use-o para encontrar outras sessões em execução com o mesmo diretório de trabalho.
Regras de cessão uma vez que uma colisão é confirmada:
- O lado que commitou primeiro permanece o escritor. O retardatário para de escrever: continuar a commitar ou trocar de branches move o HEAD da outra sessão e quebra ambas.
- O retardatário adiciona valor como verificador: revisão adversarial da saída do escritor (afirmações de doc vs código, reexecução de testes, verificações pré-publicação) e entregáveis somente em chat que não tocam em arquivos.
- Limpe apenas refs intermediários que você mesmo criou (por exemplo, um branch vazio); nunca toque em refs que a outra sessão usou.
- Logs somente de acréscimo são seguros para ambas as sessões escreverem, sob cabeçalhos de seção separados.
Prevenção: um repositório = uma sessão de escrita. Quando múltiplos agentes devem trabalhar no mesmo repositório, isole cada um em seu próprio worktree — que é o que esta habilidade faz. O hábito mais barato de detecção é re-comparar git status contra seu snapshot de início de sessão logo após você começar o trabalho.
Portabilidade
Este fluxo foi endurecido no Windows (PowerShell mais Git Bash), mas os comandos git e gh em si são multiplataforma. Onde os shells diferem, exemplos tanto para PowerShell quanto para POSIX são fornecidos. O CI inclui jobs do PowerShell 7 para as proteções de limpeza sintéticas e scanner de marcador privado no Ubuntu 24.04 e macOS 15 nativo, além dos hosts Windows. Todos os três jobs passaram no PR #7 run 30216166105. As peças específicas do Windows — junctions de diretório e suas semânticas de remoção — têm equivalentes de symlink POSIX anotados inline.
Quando Usar
- O checkout principal está sujo (WIP não commitado), desatualizado em relação ao remoto, ou compartilhado com outros agentes, e cortar um branch diretamente nele poderia quebrar esse WIP.
- Mesmo para uma pequena correção de higiene: se o checkout existente está desatualizado por muitos commits e carrega WIP sujo, um worktree isolado é a rota mais segura (testado em campo).
- Assume-se um fluxo de trabalho de um branch por tarefa (nunca commitar diretamente no branch padrão). Esta habilidade cobre apenas a mecânica de onde esse branch vive: um worktree temporário fora do checkout sujo.
Procedimento
-
Verifique o estado, de forma não destrutiva. Execute cada comando
gitcom-C <repo>e cada comandoghcom--repo <owner>/<name>, para que nada dependa do diretório atual — ambientes de agente podem redefinir o diretório de trabalho entre chamadas, e sem--repo,ghfalha com "não é um repositório git" quando invocado fora do repositório.git -C <repo> status --short # registre o WIP pré-trabalho (verificado inalterado no final) git -C <repo> branch --show-current git -C <repo> fetch origin gh repo view <owner>/<name> --json defaultBranchRef -q .defaultBranchRef.name # use o resultado como <default> abaixoOs mesmos comandos funcionam inalterados em shells POSIX.
-
Crie um worktree temporário a partir do branch padrão do
origin, sem tocar no checkout sujo. Substitua<default>do passo 1 — não codifiquemain; repositórios cujo padrão émasterainda são comuns.git -C <repo> worktree add -b fix/<task> ..\_worktrees\<task> origin/<default>POSIX:
git -C <repo> worktree add -b fix/<task> ../_worktrees/<task> origin/<default>Coloque o worktree em algum lugar confirmado como gravável. Diretórios temporários do sistema às vezes não são graváveis a partir de ambientes de agente em sandbox (testado em campo). Prefira um diretório
_worktrees/próximo ao repositório ou ao diretório temporário designado pelo seu ambiente, e antes de executar qualquer coisa pesada, escreva um arquivo de teste e o delete para confirmar acesso de gravação. -
Mantenha o diff mínimo. Edite (ou copie) apenas os arquivos que a tarefa precisa, dentro do worktree. Não carregue o WIP não rastreado do checkout principal em massa — upstream ou outros agentes podem adicionar arquivos com os mesmos nomes, e arquivos extras causam conflitos add/add no momento do merge ou poluem o diff do PR. Se você deve incluir uma cópia de um arquivo de dependência não commitado do checkout base, declare no corpo do PR qual lado vence no momento do merge (lição de campo: fazer isso explicitamente evitou uma auto-resolução ruim).
-
Lide com lacunas de dependências em worktrees novos. Um worktree novo não tem
node_modulesnem virtualenv (testado em campo).-
Node: execute
npm cino worktree (requer um lockfile). Para verificação leve apenas, um link temporário para onode_modulesdo checkout principal também funciona — mas remova o link antes da limpeza (condição de segurança 5). Junction do PowerShell:New-Item -ItemType Junction -Path <worktree>\node_modules -Target <repo>\node_modules # A remoção deve deletar apenas o link, nunca o alvo: use rmdir. cmd /c rmdir "<worktree>\node_modules"Symlink POSIX:
ln -s <repo>/node_modules <worktree>/node_modules # Remova o link em si com rm simples (sem -r, sem barra no final). rm <worktree>/node_modules -
Python: reutilize o interpretador do virtualenv do repositório principal e aponte-o para os fontes do worktree, executando apenas os testes alvo. PowerShell:
$env:PYTHONPATH = "<worktree>\src" & <repo>\.venv\Scripts\python.exe -m pytest <worktree>\tests\<target_test>.pyPOSIX:
PYTHONPATH=<worktree>/src <repo>/.venv/bin/python -m pytest <worktree>/tests/<target_test>.py
-
-
Quando a verificação passar: faça commit, push e crie o PR. Escreva a mensagem de commit em um arquivo e passe-a com
git commit -F <msgfile>— mensagens inline de múltiplas linhas são frágeis sob quoting de shell, especialmente no Windows.git -C <worktree> add <target files only> git -C <worktree> commit -F <msgfile> git -C <worktree> push -u origin fix/<task> gh pr create --repo <owner>/<name> --head fix/<task> ... -
Aguarde o CI de proteção de branch com polling limitado.
gh pr checks --watché uma espera ilimitada — harnesses de agente e camadas de aprovação podem rejeitá-lo ou travar nele (testado em campo). Use verificações únicas com um limite de tentativas:gh pr checks <pr-number> --repo <owner>/<name> --watch=false gh pr view <pr-number> --repo <owner>/<name> --json state,statusCheckRollupLoop limitado, POSIX. Onde o harness bloqueia
sleepem primeiro plano (Claude Code faz isso), execute o loop como uma tarefa em segundo plano, ou pule o loop e intercale verificações únicas entre outras peças de trabalho:for i in $(seq 1 10); do gh pr checks <pr-number> --repo <owner>/<name> --watch=false && break; sleep 30; doneLoop limitado, PowerShell:
for ($i = 1; $i -le 10; $i++) { gh pr checks <pr-number> --repo <owner>/<name> --watch=false if ($LASTEXITCODE -eq 0) { break } Start-Sleep -Seconds 30 }O limite e intervalo corretos dependem da duração do CI do repositório (não verificado em geral — ajuste por repositório).
-
Faça o merge — e decida o modo de merge primeiro, porque ele determina a verificação de trabalho não mesclado pré-limpeza (condição de segurança 2):
--merge(commit de merge): os commits do branch se tornam ancestrais do branch padrão, então verifique commerge-base --is-ancestore delete combranch -d(condição de segurança 2a; testado em campo).--squash/--rebase: os commits do branch nunca se tornam ancestrais do branch padrão, então--is-ancestorno branch sai com 1 para sempre, mesmo após um merge completamente bem-sucedido. Use a guarda "MERGED + headRefOid coincide" (condição de segurança 2b).
gh pr merge <pr-number> --repo <owner>/<name> --merge # repositórios com política de squash: --squash, então use guarda 2b gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt,mergeCommitComportamentos conhecidos em torno de
gh pr merge(testado em campo):- Se o branch padrão local está com checkout em outro worktree,
gh pr merge --delete-branchexecutado de dentro do repositório pode falhar durante seu pós-processamento local mesmo que o merge remoto tenha sido bem-sucedido. Executar o merge com--repode fora do repositório pula o pós-processamento local completamente, evitando a classe de falha logo de início. - Se uma falha for relatada mesmo assim, verifique
gh pr view --json stateprimeiro — o lado remoto geralmente já estáMERGED. Se sim, termine as etapas locais restantes (deleção de branch, remoção do worktree) manualmente após as condições de segurança abaixo. gh pr mergeexecutado de dentro do repositório pode fazer fast-forward do worktree atual para o branch padrão; ver grandes atualizações de PRs anteriores em sua saída não é uma anomalia.
Condições de Segurança
Antes de qualquer etapa destrutiva (remoção de worktree, deleção de branch, prune), verifique todas as seguintes no momento da execução — execute a verificação ANTES da deleção, nunca depois. Pular uma verificação pré-deleção e "verificar depois" derrota a proteção: um erro de trabalho não mesclado só apareceria depois que o branch já tivesse sumido (lição de campo). Em checkouts compartilhados, o estado pode mudar entre quando uma etapa foi aprovada e quando ela é executada; se o tempo passou, reexecute as verificações imediatamente antes de executar (testado em campo).
-
gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAtreportaMERGED. -
Verificação de trabalho não mesclado correspondente ao modo de merge. Não misture os modos: após um squash, a guarda 2a nunca passa, mesmo para um PR corretamente mesclado; e
branch -dapós um squash ou passa despercebido pela lógica "merged-to-upstream" (seorigin/fix/...ainda existir — o que não é garantia de ancestralidade) ou é recusado para sempre como "não totalmente mesclado" (se o upstream tiver sumido).- 2a — Modo
--merge(commit de merge):git -C <repo> merge-base --is-ancestor fix/<task> origin/<default>sai com 0 (verifique$LASTEXITCODEno PowerShell,echo $?em shells POSIX). Delete o branch com-d, para que o próprio git ainda recuse se algo foi deixado não mesclado (testado em campo). Caveat:-djulga "mesclado" contra o upstream do branch, se definido, caso contrário contra HEAD — então, após o branch remoto ser deletado e podado, e com o HEAD do checkout sentado em um branch mais antigo e não relacionado,-dpode recusar comnot fully mergedmesmo que o PR tenha mesclado corretamente (testado em campo). Nesse caso, deletar com-Dé aceitável apenas imediatamente após a verificação--is-ancestorter saído com 0 — a mesma forma guarda-então--Dda 2b; um-Dsem a guarda permanece proibido. - 2b — Modo
--squash/--rebase: obtenhastate,mergeCommiteheadRefOidviagh pr view <pr-number> --repo <owner>/<name> --json state,mergeCommit,headRefOid. Observe quemergeCommité um objeto JSON — o hash do commit é seu campooid(-q .mergeCommit.oido extrai);headRefOidé uma string simples. Em seguida, confirme ambos: (i)git -C <repo> merge-base --is-ancestor <mergeCommit-oid> origin/<default>sai com 0, e (ii)git -C <repo> rev-parse fix/<task>é igual aheadRefOid. A verificação (ii) é a substituição do modo squash para "nenhum commit deixado para trás": prova que o HEAD do PR mesclado é exatamente a ponta do seu branch local. Somente quando ambos forem verdadeiros, delete com umgit branch -Dexplícito (esta guarda substitui-d). A topologia da guarda 2b e os caminhos de rejeição são cobertos por históricos Git sintéticos descartáveis emscripts/test-cleanup-guards.ps1. O teste isola a configuração de Git sistema/global, hooks, assinatura erebase.updateRefs; captura, limpa e restaura cada variávelGIT_*ambiente para que caminhos de repositório/rastreamento não possam escapar do fixture; e restringe a limpeza recursiva ao seu filho temporário direto gerado com comparação de caminho consciente de SO e rejeição de reparse point. O caminho de squash do GitHub ao vivo foi medido em 2026-07-23 com PR #2:MERGED,mergeCommitincorporado, pontas local e remota inalteradas correspondendo aheadRefOid,-Dlocal protegido e deleção remota explícita, todos passaram. O caminho de rebase do GitHub ao vivo foi medido em 2026-07-26 com PR #5:MERGED, ummergeCommitreescrito incorporado, o HEAD original fora da ancestralidade do branch padrão, pontas local e remota inalteradas correspondendo aheadRefOid,-Dlocal protegido, deleção remota explícita e limpeza do worktree próprio passaram.
- 2a — Modo
-
O hash de merge (
mergeCommit.oid) não é nulo ou vazio. -
O branch local está rastreando um remoto (opcional — aplica-se antes de
branch -d).git -C <repo> rev-parse --abbrev-ref fix/<task>@{upstream}não falha. Em um worktree independente sem upstream definido, esta condição pode não ser atendida; pule-a e confie nas condições 1 e 2. -
O worktree não retém caminhos com links para fora do repositório (ou seja, junction de node_modules, virtualenv vinculado, etc.). Esta condição aplica-se antes da remoção do worktree. Remova quaisquer junctions e symlinks primeiro, depois remova o worktree com:
git worktree remove <worktree>Se o git se recusar a remover por causa de árvores de trabalho sujas ou modificações não commitadas, o worktree deve ser mantido ou, após verificação explícita (por exemplo, um diff vazio contra seu branch base), forçado a remover com
git worktree remove --force. Forçar só é aceitável quando você verificou que o estado guardado pelo worktree é um lixo seguro de descartar. -
Dentro da sua sessão, o caminho do worktree — e sua permissão de exclusividade como child do diretório
_worktrees/— é confirmado como segurança contra deleção acidental de um diretório ao lado. Uma verificação de que o worktree é um subdiretório direto de_worktrees/e não um caminho arbitrário impede seu script de, por exemplo, remover um junction que aponta para fora. Para um fechamento mais restrito, verifique manualmente que o worktree tem exatamente um pai diretório antes de remover. -
Nenhum outro agente está ativo no mesmo worktree. Se o seu ambiente fornece listagem de sessões, liste-as e confirme que nenhuma outra sessão está operando no mesmo caminho de worktree. Se a coordenação entre agentes não for possível, esta condição é ignorada e você confia no bloqueio implícito do worktree: worktrees git nunca compartilham um caminho, portanto a limpeza de uma sessão não pode colidir com a árvore de outra (a menos que uma sessão tenha roubado o caminho de outra — o que é improvável quando cada worktree vive em seu próprio caminho com task id).
Etapas Pós-Merge: Limpeza
Depois que todas as condições de segurança acima forem verdadeiras (execute as verificações novamente no momento da limpeza — o estado pode ter mudado desde a verificação inicial):
# Remover o worktree.
git -C <repo> worktree remove ..\_worktrees\<task>
# Poda de referências de worktree órfãs.
git -C <repo> worktree prune
# Excluir o branch remoto.
git -C <repo> push origin --delete fix/<task>
POSIX:
git -C <repo> worktree remove ../_worktrees/<task>
git -C <repo> worktree prune
git -C <repo> push origin --delete fix/<task>
Comportamento conhecido: git worktree prune do lado de fora do worktree pode não limpar suas referências órfãs; executá-lo de dentro do repositório de trabalho resolve isso (testado em campo).
Marcadores Privados e Gadgets de Rastreamento
Para detectar quando o ambiente do seu agente entra em um checkout que já está em uso por outra sessão — e que pode ter sido modificado — você pode escrever um arquivo marcador privado no checkout no início da sua sessão e verificá-lo antes de qualquer operação destrutiva. O conteúdo do arquivo deve ser um nonce (string aleatória) que você gera no início da sua sessão e espera encontrar intacto. Se outro agente sobrescrever ou remover o marcador, você saberá que o checkout foi alterado.
# Criar marcador no início da sua sessão.
$nonce = [System.IO.Path]::GetRandomFileName()
Set-Content -Path <repo>\.agent-session -Value $nonce -NoNewline
# Verificar antes de limpeza: o conteúdo deve corresponder.
if ((Get-Content <repo>\.agent-session -Raw) -eq $nonce) {
Write-Host "Marcador de sessão intacto — o checkout não foi tocado por outro agente."
} else {
Write-Warning "Marcador de sessão alterado ou ausente — outro agente pode ter modificado o checkout. Abortando limpeza."
exit 1
}
POSIX:
# Criar marcador.
dd if=/dev/random bs=16 count=1 2>/dev/null | base64 > <repo>/.agent-session
# Verificar antes de limpeza: o arquivo deve existir e conter o nonce que você escreveu.
if [ -f <repo>/.agent-session ] && [ "$(cat <repo>/.agent-session)" = "$nonce" ]; then
echo "Marcador de sessão intacto."
else
echo "AVISO: Marcador de sessão alterado ou ausente. Abortando." >&2
exit 1
fi
Nota: Guardas de marcador são uma camada extra de segurança; elas não substituem as verificações de condição de segurança mais fundamentais acima (PR mesclado, merge-base, headRefOid, etc.).
Nota Sobre Truncamento
A documentação acima descreve um fluxo de trabalho de engenharia de precisão. Se você encontrar menções a uma "versão truncada" ou "marcador de trancamento" na saída do seu agente e não conseguir encontrar o conteúdo referenciado, o fluxo completo está definido neste arquivo SKILL.md. O modelo operacional correto é: use as etapas completas conforme documentado aqui, não uma versão resumida.