Issues y pull requests de GitHub¶
Curupira descubre issues y pull requests de GitHub con filtros tipados en TOML, los compila
en consultas de GitHub Search
y ejecuta esas consultas mediante la API GraphQL. La autenticación es un bearer token
obtenido con gh auth token. Curupira no crea, edita, cierra ni comenta issues o pull
requests.
Cada coincidencia se convierte en una tarea: un elemento tipado (campos de issue o de pull request), una identidad usada para la deduplicación, un prompt renderizado para una CLI de agente de código y un checkout o worktree local de Git donde se ejecuta ese agente.
| Tipo de trigger | Qué descubre |
|---|---|
plugins-py/github-issues |
Issues de un owner/repository |
plugins-py/github-pull-requests |
Pull requests de un owner/repository |
plugins-py/github-project-items |
Issues y pull requests en una columna de un tablero de GitHub Projects. Consulta GitHub Projects |
Requisitos previos¶
- Instala la CLI de GitHub (
gh). - Autentícate una vez:
- Confirma que
ghtiene un token (Curupira lo lee con el mismo comando):
Curupira no almacena credenciales de GitHub. Ejecuta gh auth token (con caché de cinco
minutos) y envía ese bearer token a https://api.github.com/graphql. Las operaciones de
clon y worktree usan git nativo y tus credenciales de Git habituales para el remote
del repositorio, no gh repo clone.
Alcances del token¶
Usa un token con el alcance repo para repositorios privados, o public_repo si solo
necesitas repositorios públicos. Revisa gh auth status para ver los alcances de tu
sesión actual. Curupira solo lee resultados de Search; el agente de código que lanza puede
hacer más, según su perfil. Consulta
la guía de autenticación de GraphQL de GitHub
para saber cómo se relacionan los datos solicitados con los alcances.
Automatización mínima de issues¶
En una automatización, repo es el owner/name de GitHub que Curupira busca.
repository es el alias de un checkout [repositories.<alias>] donde se ejecuta el
agente. Pueden ser distintos.
[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}"
Valida sin contactar con GitHub:
Automatización mínima de pull requests¶
repo y repository significan lo mismo que en Automatización 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}
"""
Automatización de pull requests de Dependabot¶
Filtra por un solo autor con author = "app/dependabot" y exige mergeable = true para
que los pull requests con conflictos se dejen en paz. El prompt siguiente solo usa
placeholders listados arriba, y ${repo} lleva el owner/name que se usa en cada comando
gh. El perfil del agente necesita auto_approve = true para que las escrituras de gh
sin supervisión (push, gh pr merge) no se detengan a pedir aprobación.
[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`.
"""
Ejecútalo hasta que se hayan gestionado los pull requests abiertos de Dependabot:
Primera ejecución¶
- Instala y autentica la CLI del agente de código indicada en tu perfil (
opencodeen los fragmentos anteriores). Las opciones nativas de cada proveedor y las notas de configuración están en las páginas de Proveedores y agentes. - Previsualiza una tarea coincidente sin clonar ni iniciar un agente:
- Procesa una vez las tareas disponibles en ese momento, o consulta de forma continua:
--dry-run no se puede combinar con --watch ni con --size. validate comprueba la
estructura del TOML y la configuración del trigger sin llamar a GitHub; run y
run --watch necesitan gh y la CLI del agente elegido instaladas y autenticadas.
Campos de configuración¶
Los modelos rechazan las claves desconocidas.
Campos compartidos de la automatización¶
| Campo | Valor por defecto | Notas |
|---|---|---|
trigger_type |
ver notas | Defínelo explícitamente como "plugins-py/github-issues" o "plugins-py/github-pull-requests". Si se omite al interpretar el TOML, Curupira elige "plugins-py/github-issues" solo cuando la tabla no tiene schedule; una clave schedule hace que el valor por defecto sea "cron" |
repository |
(obligatorio) | Alias de una entrada [repositories.<alias>] |
checkout |
"worktree" |
"worktree" o "main" (checkout compartido, no un nombre de rama) |
prompt |
(obligatorio) | string.Template no vacío con ${placeholders} |
profile |
None |
Perfil de CLI con nombre; si no, agents.defaults.profile, que por defecto es "opencode". Consulta Proveedores y agentes para ver las opciones de perfil |
Filtros compartidos de GitHub¶
Se compilan en la consulta de Search.
| Campo | Valor por defecto | Calificador de Search / comportamiento |
|---|---|---|
repo |
(obligatorio) | repo:owner/name (formato owner/repository) |
state |
"open" |
"open" → is:open; "closed" → is:closed; "all" → ninguno. En los pull requests, is:closed incluye los PR con merge |
labels |
() |
Un label o una lista. Cada valor → label:… (AND: deben estar presentes todos los labels) |
exclude_labels |
() |
Un label o una lista. Cada valor → -label:… |
assignee |
None |
Login, @me, none → no:assignee, o any → assignee:* |
author |
None |
author:… |
milestone |
None |
milestone:… |
project |
None |
project:<value>, por ejemplo acme/5 |
sort |
"created-asc" |
Se añade como sort:…. Valores permitidos: created-asc, created-desc, updated-asc, updated-desc, comments-asc, comments-desc |
El calificador project solo restringe a qué proyecto pertenece un elemento. Para elegir
elementos por columna del tablero, usa github-project-items. Los
valores con espacios en blanco o con ", :, , se entrecomillan en la consulta
compilada.
Campos solo para issues¶
| Campo | Valor por defecto | Comportamiento |
|---|---|---|
linked_pull_request |
None |
true → linked:pr; false → -linked:pr; omítelo para no filtrar |
Campos solo para pull requests¶
| Campo | Valor por defecto | Comportamiento |
|---|---|---|
draft |
None |
true → draft:true; false → draft:false |
base |
None |
base:… |
head |
None |
head:… |
review |
None |
review:… — none, required, approved o changes_requested |
ci_status |
None |
status:… — success, failure o pending |
linked_issue |
None |
true → linked:issue; false → -linked:issue |
mergeable |
None |
Posfiltro de GraphQL: true conserva MERGEABLE; false conserva CONFLICTING |
merge_state |
() |
Posfiltro de GraphQL: conserva solo los valores de mergeStateStatus listados (BEHIND, BLOCKED, CLEAN, DIRTY, DRAFT, HAS_HOOKS, UNKNOWN, UNSTABLE) |
mergeable y merge_state se aplican después de que Search devuelve resultados; no son
calificadores de Search.
Ajustes de polling relacionados¶
Bajo [settings.polling]:
| Campo | Valor por defecto |
|---|---|
poll_interval_seconds |
30.0 |
batch_size |
100 (máximo 1000) |
cron_poll_interval_seconds |
1.0 |
Cómo se seleccionan los issues y pull requests¶
Cada consulta que se construye empieza siempre con repo:<repo> y con is:issue o
is:pr, y luego añade los filtros anteriores y sort:<sort>. Ejemplo para la
automatización mínima de issues:
El descubrimiento usa GraphQL Search (el tipo de búsqueda ISSUE cubre tanto issues como
pull requests). No existe un campo query de formato libre: los filtros son solo TOML
tipado.
Deduplicación¶
El host identifica un resultado calculando un hash de la lista de pares clave/valor que devolvió el trigger, en ese orden, junto con la automatización, el repositorio y el tipo de trigger. Curupira no guarda historial de issues ni de pull requests:
- Sin duplicados en una ejecución. Un
run --watchotuien marcha despacha cada tarea una vez, termine con éxito o con fallo. Un poll posterior que devuelva los mismos pares se omite hasta que el proceso termina. - Nada se recuerda entre ejecuciones. Curupira no registra issues ni pull requests terminados. Tras un reinicio, cada elemento que siga coincidiendo con los filtros del trigger se despacha de nuevo, así que los filtros deciden qué está pendiente.
- El trabajo interrumpido se reanuda. Es el único estado de tarea que Curupira guarda. El id
de sesión del agente de código se guarda en
state_db_pathen cuanto el agente lo anuncia, y se borra cuando la tarea termina con éxito. Cuando Curupira se reinicia en medio de una tarea (o la tarea falló o agotó el tiempo), el siguiente despacho continúa esa sesión con las mismas instrucciones en lugar de empezar de nuevo. Un prompt de automatización cambiado empieza una sesión nueva.
Cambiar cualquier valor devuelto, incluidos el título, el cuerpo o los labels, crea una tarea distinta. Por
eso los triggers de GitHub no devuelven valores que cambian por sí solos o porque el
agente trabajó en el elemento (comentarios, hora de actualización, estado de CI y de revisión, pull requests vinculados):
el agente se iniciaría de nuevo para el mismo issue. Cuando el agente los necesite, indícale que los lea,
por ejemplo con gh issue view ${issue_number} --comments.
La clave es por automatización, así que dos automatizaciones pueden procesar el mismo issue o pull request con
prompts distintos. run --dry-run previsualiza sin despachar.
Dejar fuera los elementos terminados
Filtra por un estado que el agente cambia, y haz que el prompt le pida cambiarlo al terminar:
cierra el elemento, agrega o quita un label (exclude_labels = ["blocked"]) o vincula un pull
request (linked_pull_request = false).
Cuando un ciclo completo no encuentra nada (o el descubrimiento falla), el feed espera
poll_interval_seconds y luego duplica la espera en ciclos vacíos consecutivos hasta
cinco minutos (300 segundos). Cualquier descubrimiento exitoso restablece el intervalo.
Qué recibe el prompt del agente¶
El prompt puede usar ${field} o sintaxis Jinja; consulta Escribir prompts.
Todos los campos de issues y pull requests (título, cuerpo, labels, asignados, hito, tamaño del diff y más) están listados en la
Referencia de campos del prompt.
Seguridad: el texto de issues y pull requests no es confiable¶
Los títulos y cuerpos (${issue_title}, ${issue_body}, ${pull_request_title},
${pull_request_body}, ${item_body}) vienen de cualquier persona que pueda abrir un issue o pull request y
se colocan en el prompt del agente de código, así que un autor hostil puede intentar
dirigir al agente (prompt injection). El agente puede ejecutar acciones destructivas en el
checkout donde trabaja, por lo que el riesgo depende de lo que permitas en el perfil del
agente. Restringe lo que llega al agente con labels (aplicados solo por personas de
confianza), author o assignee, y revisa qué puede ejecutar tu perfil de agente.
Checkout y worktrees¶
Los triggers de GitHub usan el control de versiones nativo de Git por defecto.
- Curupira clona
repositories.<alias>.remoteenworkspace_dir/<alias>(o en elpathdel alias) la primera vez que se usa. - Con
checkout = "worktree"(por defecto), hace fetch deorigin, resuelve la rama predeterminada remota y añade un worktree en<checkout>.worktrees/<automation_id>/<task_type>-<sha256(task_id)>sobre la ramacurupira/<automation_id>/<task_type>-<sha256(task_id)>. La rama no se sube con push. - Con
checkout = "main", el agente se ejecuta en el checkout compartido tal como está: sin fetch, pull ni cambio de rama. - Cuando el agente termina, Curupira elimina el worktree y borra la rama local (los fallos de limpieza se registran en el log, no son fatales).
El setup_script opcional del alias del repositorio se ejecuta solo después de un clon
nuevo del checkout base, no dentro del worktree.
Aislamiento del agente¶
El proceso del agente se ejecuta con su directorio de trabajo y PWD apuntando al
worktree, y sin las variables GIT_DIR, GIT_WORK_TREE y GIT_INDEX_FILE que harían
que Git apunte a otro repositorio. Una automatización nunca cambia tu propio checkout.
OpenCode 1.x también recibe --dir <worktree>. OpenCode 2.x no tiene --dir; una
ejecución headless usa el worktree como directorio de trabajo y pasa --standalone, así
que la tarea no se une al servicio en segundo plano compartido. OpenCode 1.x también recibe
OPENCODE_DB definido como <worktree>.opencode.db, de modo que sus sesiones y entradas de
proyecto permanezcan con esa tarea. OpenCode 2.x conserva tu base de datos, donde guarda las
credenciales de los proveedores. Otro estado global del agente, como la configuración global de
OpenCode, se comparte.
Errores comunes¶
| Síntoma | Causa probable | Solución |
|---|---|---|
Required executable not found: gh |
La CLI de GitHub no está en el PATH |
Instala gh y vuelve a intentarlo |
gh exited with status … / token vacío |
No hay sesión iniciada o el token no está disponible | Ejecuta gh auth login y luego gh auth token |
| Errores GraphQL / HTTP 401 o de alcances | El token no tiene acceso al repositorio o a Search | Vuelve a ejecutar gh auth login para que el token incluya repo, o usa un PAT clásico con los alcances que exige la guía de GraphQL de GitHub |
| No hay tareas programadas | Los filtros no coinciden con nada | Amplía labels/state, quita los filtros linked_* / draft o confirma que repo es correcto; los ciclos vacíos no son un error |
GitHub GraphQL temporarily failed with status 429 (o similar) |
Límite de tasa o fallo transitorio de la API | Curupira reintenta los errores transitorios unas cuantas veces; espera o reduce la frecuencia de polling / batch_size |
Advertencias de descubrimiento en run --watch |
Un fallo de GraphQL o de la CLI se trata como ciclo vacío | Revisa los logs en busca de Discovery failed for …; corrige la autenticación o la red y espera al siguiente poll |
curupira validate comprueba la estructura del TOML y la configuración del trigger sin
llamar a GitHub. curupira run y curupira run --watch necesitan gh instalado y
autenticado.