CommunityEscrita e Ediçãogithub.com

h8nc4y/isolated-worktree-pr-flow

Habilidade para Claude Code e Codex: criar PRs a partir de um worktree git temporário baseado no branch padrão do origin quando o checkout principal está sujo, desatualizado ou compartilhado com outros agentes — com proteções de limpeza conscientes do modo de merge.

O que é isolated-worktree-pr-flow?

Esta habilidade fornece um fluxo de trabalho robusto para criação de pull requests a partir de um worktree git temporário, isolado do checkout principal. O caso de uso principal é quando o checkout principal está sujo (com trabalho não commitado), desatualizado em relação ao repositório remoto, ou compartilhado entre múltiplas sessões de agentes de IA (como Claude Code ou Codex). O procedimento abrange a criação do worktree, o tratamento de dependências ausentes (como node_modules ou virtualenv), polling limitado de CI usando o `gh` CLI, e verificações de segurança pré-limpeza que são sensíveis ao modo de merge (`--merge`, `--squash` ou `--rebase`). A habilidade é portável entre Windows e plataformas POSIX, com exemplos específicos para cada shell (PowerShell e bash). Inclui orientações detalhadas para detectar e resolver colisões entre agentes que compartilham o mesmo checkout, promovendo a prevenção através do uso de worktrees isolados.

Funciona com✓Claude Code✓Codex CLI~Cursor
npx skills add h8nc4y/isolated-worktree-pr-flow

Installed? Explore more Escrita e Edição skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

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):

  1. Sinais: arquivos que você não escreveu aparecem, ou git status não corresponde mais ao snapshot que você tirou quando sua sessão começou.
  2. 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.
  3. git reflog --date=iso estabelece a linha do tempo: quem fez checkout, commitou ou mergeou, e quando.
  4. 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

  1. Verifique o estado, de forma não destrutiva. Execute cada comando git com -C <repo> e cada comando gh com --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, gh falha 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> abaixo
    

    Os mesmos comandos funcionam inalterados em shells POSIX.

  2. 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 codifique main; repositórios cujo padrão é master ainda 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.

  3. 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).

  4. Lide com lacunas de dependências em worktrees novos. Um worktree novo não tem node_modules nem virtualenv (testado em campo).

    • Node: execute npm ci no worktree (requer um lockfile). Para verificação leve apenas, um link temporário para o node_modules do 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>.py
      

      POSIX:

      PYTHONPATH=<worktree>/src <repo>/.venv/bin/python -m pytest <worktree>/tests/<target_test>.py
      
  5. 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> ...
    
  6. 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,statusCheckRollup
    

    Loop limitado, POSIX. Onde o harness bloqueia sleep em 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; done
    

    Loop 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).

  7. 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 com merge-base --is-ancestor e delete com branch -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-ancestor no 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,mergeCommit
    

    Comportamentos 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-branch executado 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 --repo de 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 state primeiro — 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 merge executado 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).

  1. gh pr view <pr-number> --repo <owner>/<name> --json state,mergedAt reporta MERGED.

  2. 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 -d após um squash ou passa despercebido pela lógica "merged-to-upstream" (se origin/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 $LASTEXITCODE no 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: -d julga "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, -d pode recusar com not fully merged mesmo que o PR tenha mesclado corretamente (testado em campo). Nesse caso, deletar com -D é aceitável apenas imediatamente após a verificação --is-ancestor ter saído com 0 — a mesma forma guarda-então--D da 2b; um -D sem a guarda permanece proibido.
    • 2b — Modo --squash / --rebase: obtenha state, mergeCommit e headRefOid via gh pr view <pr-number> --repo <owner>/<name> --json state,mergeCommit,headRefOid. Observe que mergeCommit é um objeto JSON — o hash do commit é seu campo oid (-q .mergeCommit.oid o 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 a headRefOid. 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 um git branch -D explí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 em scripts/test-cleanup-guards.ps1. O teste isola a configuração de Git sistema/global, hooks, assinatura e rebase.updateRefs; captura, limpa e restaura cada variável GIT_* 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, mergeCommit incorporado, pontas local e remota inalteradas correspondendo a headRefOid, -D local 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, um mergeCommit reescrito incorporado, o HEAD original fora da ancestralidade do branch padrão, pontas local e remota inalteradas correspondendo a headRefOid, -D local protegido, deleção remota explícita e limpeza do worktree próprio passaram.
  3. O hash de merge (mergeCommit.oid) não é nulo ou vazio.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

Referências

Habilidades Relacionadas