Issues e pull requests do GitHub¶
O Curupira descobre issues e pull requests do GitHub com filtros tipados em TOML, compila-os
em consultas da GitHub Search
e executa essas consultas pela API GraphQL. A autenticação é um bearer token obtido de
gh auth token. O Curupira não cria, edita, fecha nem comenta em issues ou pull
requests.
Cada resultado vira uma tarefa: um item tipado (campos de issue ou de pull request), uma identidade usada para deduplicação, um prompt renderizado para uma CLI de agente de código e um checkout ou worktree Git local onde esse agente roda.
| Tipo de trigger | O que ele descobre |
|---|---|
plugins-py/github-issues |
Issues em um único owner/repository |
plugins-py/github-pull-requests |
Pull requests em um único owner/repository |
plugins-py/github-project-items |
Issues e pull requests em uma coluna de um board do GitHub Projects. Veja GitHub Projects |
Pré-requisitos¶
- Instale a GitHub CLI (
gh). - Autentique-se uma vez:
- Confirme que o
ghtem um token (o Curupira o lê com o mesmo comando):
O Curupira não armazena credenciais do GitHub. Ele executa gh auth token (com cache de cinco
minutos) e envia esse bearer token para https://api.github.com/graphql. As operações de clone
e worktree usam o git nativo e suas credenciais Git normais para o remote do repositório, não
gh repo clone.
Escopos do token¶
Use um token com o escopo repo para repositórios privados, ou public_repo quando você só
precisar de repositórios públicos. Veja gh auth status para saber os escopos do seu login atual.
O próprio Curupira apenas lê resultados da Search; o agente de código que ele inicia pode fazer mais,
dependendo do perfil. Veja
o guia de autenticação do GraphQL do GitHub
para entender como os dados solicitados se relacionam com os escopos.
Automação mínima de issues¶
Em uma automação, repo é o owner/name do GitHub que o Curupira pesquisa. repository é
o alias de um checkout [repositories.<alias>] onde o agente roda. Eles podem ser diferentes.
[repositories.api]
remote = "https://github.com/acme/api.git"
[agents.defaults]
profile = "opencode-default"
timezone = "UTC"
[agents.profiles.opencode-default]
provider = "opencode"
[automations.resolve-ready-issues]
trigger_type = "plugins-py/github-issues"
repository = "api"
repo = "acme/api"
labels = ["agent-ready"]
linked_pull_request = false
prompt = "Resolve issue ${issue_number}: ${issue_title}\n\n${issue_body}"
Valide sem contatar o GitHub:
Automação mínima de pull requests¶
repo e repository têm o mesmo significado de Automação mínima de issues.
[repositories.api]
remote = "https://github.com/acme/api.git"
[agents.defaults]
profile = "opencode-default"
timezone = "UTC"
[agents.profiles.opencode-default]
provider = "opencode"
[automations.review-pull-requests]
trigger_type = "plugins-py/github-pull-requests"
repository = "api"
repo = "acme/api"
labels = ["review-needed"]
draft = false
prompt = """
Review pull request ${pull_request_number}: ${pull_request_title}
${pull_request_body}
Branch: ${pull_request_head_ref} -> ${pull_request_base_ref}
"""
Automação de pull requests do Dependabot¶
Filtre por um único autor com author = "app/dependabot" e exija mergeable = true para que
pull requests com conflito sejam ignorados. O prompt abaixo usa apenas placeholders listados acima,
e ${repo} carrega o owner/name usado em todos os comandos gh. O perfil do agente precisa de
auto_approve = true para que as escritas do gh sem supervisão (push, gh pr merge) não parem para aprovação.
[automations.merge-dependabot-pull-requests]
trigger_type = "plugins-py/github-pull-requests"
repository = "curupira"
repo = "caipora-labs/curupira"
author = "app/dependabot"
state = "open"
draft = false
mergeable = true
sort = "created-asc"
prompt = """
Analyze Dependabot pull request #${pull_request_number} (${pull_request_title}) in ${repo}.
Fix what the update breaks, wait for the checks, then squash-merge it with
`gh pr merge ${pull_request_number} --repo ${repo} --squash`.
"""
Execute até que os pull requests abertos do Dependabot sejam tratados:
Primeira execução¶
- Instale e autentique a CLI do agente de código indicada pelo seu perfil (
opencodenos trechos acima). As opções nativas de cada provider e as notas de configuração estão nas páginas Providers e agentes. - Faça uma prévia de uma tarefa correspondente, sem clonar nem iniciar um agente:
- Esvazie as tarefas atualmente disponíveis uma vez, ou consulte continuamente:
--dry-run não pode ser combinado com --watch nem com --size. validate verifica a
estrutura do TOML e a configuração do trigger sem chamar o GitHub; run e run --watch exigem
gh e a CLI do agente escolhido instalados e autenticados.
Campos de configuração¶
Os modelos rejeitam chaves desconhecidas.
Campos compartilhados da automação¶
| Campo | Padrão | Observações |
|---|---|---|
trigger_type |
veja as observações | Defina explicitamente como "plugins-py/github-issues" ou "plugins-py/github-pull-requests". Se omitido ao interpretar o TOML, o Curupira escolhe "plugins-py/github-issues" somente quando a tabela não tem schedule; uma chave schedule faz o padrão ser "cron" |
repository |
(obrigatório) | Alias de uma entrada [repositories.<alias>] |
checkout |
"worktree" |
"worktree" ou "main" (checkout compartilhado, não o nome de uma branch) |
prompt |
(obrigatório) | string.Template não vazio com ${placeholders} |
profile |
None |
Perfil de CLI nomeado; caso contrário, agents.defaults.profile, cujo padrão é "opencode". Veja Providers e agentes para as opções de perfil |
Filtros compartilhados do GitHub¶
Compilados na consulta da Search.
| Campo | Padrão | Qualificador da Search / comportamento |
|---|---|---|
repo |
(obrigatório) | repo:owner/name (formato owner/repository) |
state |
"open" |
"open" → is:open; "closed" → is:closed; "all" → nenhum dos dois. Para pull requests, is:closed inclui PRs com merge feito |
labels |
() |
Uma label ou uma lista. Cada valor → label:… (AND: todas as labels devem estar presentes) |
exclude_labels |
() |
Uma label ou uma lista. Cada valor → -label:… |
assignee |
None |
Login, @me, none → no:assignee, ou any → assignee:* |
author |
None |
author:… |
milestone |
None |
milestone:… |
project |
None |
project:<value>, por exemplo acme/5 |
sort |
"created-asc" |
Adicionado como sort:…. Permitidos: created-asc, created-desc, updated-asc, updated-desc, comments-asc, comments-desc |
O qualificador project apenas restringe a qual projeto um item pertence. Para escolher itens por
coluna do board, use github-project-items. Valores com espaço em branco ou ", :, , são colocados entre aspas na
consulta compilada.
Campos exclusivos de issues¶
| Campo | Padrão | Comportamento |
|---|---|---|
linked_pull_request |
None |
true → linked:pr; false → -linked:pr; omita para não filtrar |
Campos exclusivos de pull requests¶
| Campo | Padrão | Comportamento |
|---|---|---|
draft |
None |
true → draft:true; false → draft:false |
base |
None |
base:… |
head |
None |
head:… |
review |
None |
review:… — none, required, approved ou changes_requested |
ci_status |
None |
status:… — success, failure ou pending |
linked_issue |
None |
true → linked:issue; false → -linked:issue |
mergeable |
None |
Pós-filtro do GraphQL: true mantém MERGEABLE; false mantém CONFLICTING |
merge_state |
() |
Pós-filtro do GraphQL: mantém apenas os valores de mergeStateStatus listados (BEHIND, BLOCKED, CLEAN, DIRTY, DRAFT, HAS_HOOKS, UNKNOWN, UNSTABLE) |
mergeable e merge_state são aplicados depois que a Search retorna os resultados; não são
qualificadores da Search.
Configurações de polling relacionadas¶
Em [settings.polling]:
| Campo | Padrão |
|---|---|
poll_interval_seconds |
30.0 |
batch_size |
100 (máximo 1000) |
cron_poll_interval_seconds |
1.0 |
Como issues e pull requests são selecionados¶
Cada poll monta uma consulta que sempre começa com repo:<repo> e com is:issue ou
is:pr, depois acrescenta os filtros acima e sort:<sort>. Exemplo para a automação mínima
de issues:
A descoberta usa a Search do GraphQL (o tipo de busca ISSUE cobre tanto issues quanto pull requests).
Não existe um campo query livre: os filtros são apenas TOML tipado.
Deduplicação¶
O host identifica um resultado fazendo o hash da lista de chaves/valores que o trigger retornou, nessa ordem, junto com a automação, o repositório e o tipo do trigger. O Curupira não guarda histórico de issues nem de pull requests:
- Sem duplicatas numa execução. Um
run --watchoutuiem andamento despacha cada tarefa uma vez, termine ela com sucesso ou com falha. Uma consulta posterior que retorne os mesmos pares é ignorada até o processo terminar. - Nada é lembrado entre execuções. O Curupira não registra issues nem pull requests concluídos. Depois de uma reinicialização, todo item que ainda corresponda aos filtros do trigger é despachado de novo, então os filtros decidem o que está pendente.
- Trabalho interrompido é retomado. Este é o único estado de tarefa que o Curupira guarda. O
id da sessão do agente de código é salvo em
state_db_pathassim que o agente o informa, e removido quando a tarefa termina com sucesso. Quando o Curupira reinicia no meio de uma tarefa (ou a tarefa falhou ou estourou o tempo), o próximo despacho continua essa sessão com as mesmas instruções em vez de recomeçar. Um prompt de automação alterado começa uma sessão nova.
Alterar qualquer valor retornado, inclusive o título, o corpo ou as labels, cria uma tarefa diferente. Por
isso os triggers do GitHub não retornam valores que mudam sozinhos ou porque o agente trabalhou no item
(comentários, horário de atualização, estado do CI e da revisão, pull requests vinculados):
o agente seria iniciado novamente para a mesma issue. Quando o agente precisar deles, diga a ele que os leia,
por exemplo com gh issue view ${issue_number} --comments.
A chave é por automação, então duas automações podem processar a mesma issue ou o mesmo pull request com
prompts diferentes. run --dry-run faz a prévia sem despachar.
Manter itens concluídos de fora
Filtre por um estado que o agente altera, e faça o prompt pedir que ele o altere ao
terminar: feche o item, adicione ou remova uma label (exclude_labels = ["blocked"]) ou
vincule um pull request (linked_pull_request = false).
Quando um ciclo completo não encontra nada (ou a descoberta falha), o feed espera
poll_interval_seconds e depois dobra a espera a cada ciclo vazio consecutivo, até cinco
minutos (300 segundos). Qualquer descoberta bem-sucedida reinicia o intervalo.
O que o prompt do agente recebe¶
O prompt pode usar ${field} ou a sintaxe Jinja; veja Escrevendo prompts. Todos os campos
de issues e pull requests (título, corpo, labels, responsáveis, milestone, tamanho do diff e mais) estão listados na Referência de campos de prompt.
Segurança: o texto de issues e pull requests não é confiável¶
Títulos e corpos (${issue_title}, ${issue_body}, ${pull_request_title},
${pull_request_body}, ${item_body}) vêm de qualquer pessoa que possa abrir uma issue ou um pull request e são
colocados no prompt do agente de código, então um autor hostil pode tentar manipular o agente
(prompt injection). O agente pode executar ações destrutivas no checkout em que trabalha,
então o risco depende do que você permite no perfil do agente. Restrinja o que chega ao
agente com labels (aplicadas somente por pessoas em quem você confia), author ou assignee, e
revise o que o seu perfil de agente tem permissão para executar.
Checkout e worktrees¶
Os triggers do GitHub usam o controle de versão Git nativo padrão.
- O Curupira clona
repositories.<alias>.remoteemworkspace_dir/<alias>(ou nopathdo alias) no primeiro uso. - Com
checkout = "worktree"(padrão), ele faz fetch deorigin, resolve a branch padrão remota e adiciona uma worktree em<checkout>.worktrees/<automation_id>/<task_type>-<sha256(task_id)>na branchcurupira/<automation_id>/<task_type>-<sha256(task_id)>. A branch não é enviada com push. - Com
checkout = "main", o agente roda no checkout compartilhado como está: sem fetch, pull nem troca de branch. - Depois que o agente termina, o Curupira remove a worktree e apaga a branch local (falhas na limpeza são registradas em log, não são fatais).
O setup_script opcional no alias do repositório roda somente após um clone novo do
checkout base, não dentro da worktree.
Isolamento do agente¶
O processo do agente roda com o diretório de trabalho e o PWD definidos para a worktree, e sem
as variáveis GIT_DIR, GIT_WORK_TREE e GIT_INDEX_FILE que apontariam o Git para outro
repositório. O seu próprio checkout nunca é alterado por uma automação. O OpenCode 1.x também
recebe --dir <worktree>. O OpenCode 2.x não tem --dir; uma execução headless usa a worktree
como diretório de trabalho e passa --standalone, então a tarefa não entra no serviço em
segundo plano compartilhado. O OpenCode 1.x também recebe
OPENCODE_DB definido como <worktree>.opencode.db, para que as sessões e as entradas de projeto
fiquem com aquela tarefa. O OpenCode 2.x mantém o seu banco, onde guarda as credenciais dos
provedores. Outros estados globais do agente, como a configuração global do OpenCode, são
compartilhados.
Erros comuns¶
| Sintoma | Causa provável | Correção |
|---|---|---|
Required executable not found: gh |
GitHub CLI ausente do PATH |
Instale o gh e tente de novo |
gh exited with status … / token vazio |
Sem login, ou token indisponível | Execute gh auth login e depois gh auth token |
| GraphQL / HTTP 401 ou erros de escopo | O token não tem acesso ao repositório ou à Search | Execute gh auth login de novo para que o token inclua repo, ou use um PAT clássico com os escopos exigidos pelo guia do GraphQL do GitHub |
| No tasks scheduled | Os filtros não encontram nada | Amplie labels/state, remova filtros linked_* / draft, ou confirme que repo está correto; ciclos vazios não são um erro |
GitHub GraphQL temporarily failed with status 429 (ou similar) |
Limite de taxa ou falha transitória da API | O Curupira tenta novamente erros transitórios algumas vezes; espere ou reduza a frequência de polling / batch_size |
Avisos de descoberta em run --watch |
Falha do GraphQL ou da CLI tratada como ciclo vazio | Verifique os logs por Discovery failed for …; corrija a autenticação ou a rede e aguarde o próximo poll |
curupira validate verifica a estrutura do TOML e a configuração do trigger sem chamar o GitHub.
curupira run e curupira run --watch exigem o gh instalado e autenticado.