Saltar a contenido

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

  1. Instala la CLI de GitHub (gh).
  2. Autentícate una vez:
gh auth login
  1. Confirma que gh tiene un token (Curupira lo lee con el mismo comando):
gh auth token

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:

curupira --config path/to/settings.toml validate

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:

curupira --config path/to/settings.toml run --watch --size 12

Primera ejecución

  1. Instala y autentica la CLI del agente de código indicada en tu perfil (opencode en 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.
  2. Previsualiza una tarea coincidente sin clonar ni iniciar un agente:
curupira --config path/to/settings.toml run --dry-run
  1. Procesa una vez las tareas disponibles en ese momento, o consulta de forma continua:
curupira --config path/to/settings.toml run
curupira --config path/to/settings.toml run --watch

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

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:

repo:acme/api is:issue is:open label:agent-ready -linked:pr sort:created-asc

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 --watch o tui en 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_path en 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.

  1. Curupira clona repositories.<alias>.remote en workspace_dir/<alias> (o en el path del alias) la primera vez que se usa.
  2. Con checkout = "worktree" (por defecto), hace fetch de origin, resuelve la rama predeterminada remota y añade un worktree en <checkout>.worktrees/<automation_id>/<task_type>-<sha256(task_id)> sobre la rama curupira/<automation_id>/<task_type>-<sha256(task_id)>. La rama no se sube con push.
  3. Con checkout = "main", el agente se ejecuta en el checkout compartido tal como está: sin fetch, pull ni cambio de rama.
  4. 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.