# Pipelines que piensan, infra que no rompe: Kiro headless mode en acción (GitLab)

Por [@roxsross](https://roxs.dev) · roxs.dev

* * *

## Introducción

¿Cuántas veces mergeaste un cambio de infraestructura y te enteraste del problema en producción? ¿Cuántos security groups abiertos al mundo, cuántos IAM con `*` en `Action`, cuántos secrets hardcodeados llegaron a `main` porque nadie revisó el Terraform con suficiente detalle?

La revisión manual de infraestructura como código no escala. Los equipos crecen, los Merge Requests se acumulan, y la presión por deployar rápido termina ganándole al rigor. Lo que necesitamos es que el pipeline piense por nosotros — y exactamente eso es lo que construimos en esta demo.

En este artículo te muestro cómo armé un flujo completo de revisión automática de IaC con **Kiro headless mode**, integrado directamente en **GitLab CI/CD**, que analiza cada MR de Terraform, postea una note con hallazgos de seguridad, calidad y buenas prácticas, y además mantiene un **issue Dashboard** vivo con todos los findings. Sin intervención humana, sin herramientas externas, sin configuración compleja.

> Si venís de la versión GitHub Actions de esta demo: el concepto es idéntico, pero acá todo está portado a `.gitlab-ci.yml` y a la API REST de GitLab. Más abajo tenés una tabla de equivalencias.

* * *

## ¿Qué es Kiro headless mode?

Kiro CLI 2.0 introdujo el modo headless (sin interfaz gráfica), una forma de ejecutar `kiro-cli` de manera programática dentro de cualquier entorno automatizado.

La idea es simple: generás una API key en `kiro.dev/settings/api`, la agregás como variable de entorno, y Kiro CLI se ejecuta sin necesidad de que haya alguien sentado frente a una terminal.

Lo que lo hace poderoso:

*   **Acceso completo** a todos los agentes, herramientas y capacidades de una sesión interactiva.
    
*   **Sin intervención humana**: podés generar MRs, ejecutar reviews, troubleshootear o documentar sin que nadie esté monitoreando.
    
*   **Multiplataforma**: funciona nativamente en macOS, Linux y Windows.
    
*   Una línea para instalar:
    

```bash
curl -fsSL https://cli.kiro.dev/install | bash
```

* * *

## La demo: review automático de Terraform en cada MR

### El escenario

Tenemos un repositorio con infraestructura en Terraform para un servicio ECS Fargate en AWS. El archivo `main.tf` tiene bugs intencionales — security groups abiertos, IAM demasiado permisivo, CIDR blocks inválidos, secretos expuestos — diseñados para que Kiro los detecte.

El objetivo: que cada MR que toque `terraform/` reciba automáticamente una note con un análisis profundo de seguridad, calidad e infraestructura, sin que nadie tenga que pedirlo.

### Estructura del proyecto

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/5b6f942a-8820-4d06-a7b0-023e73d23370.png align="center")

Una diferencia clave con GitHub: los dos workflows de GitHub Actions (`kiro-review.yml` y `kiro-docs.yml`) se consolidan en **un solo** `.gitlab-ci.yml` con dos jobs (`kiro:review` y `kiro:docs`), separados por reglas (`rules`).

* * *

## Cómo funciona el pipeline de review

El job `kiro:review` es el corazón de la demo. Veamos cada parte.

### 1\. Workflow rules: cuándo corre el pipeline

En GitLab, en vez de `on: pull_request`, controlamos los disparadores con `workflow:rules` a nivel del pipeline completo:

```yaml
workflow:
  rules:
    # MR abierto/actualizado -> pipeline de merge request
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    # Push a la rama default -> pipeline de rama (para docs)
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    # Disparo manual desde la UI (Run pipeline)
    - if: '$CI_PIPELINE_SOURCE == "web"'
```

Esto, además, evita el clásico problema del **doble pipeline** (branch + MR corriendo a la vez) que sufre quien arranca con GitLab CI sin reglas.

### 2\. El job y sus reglas de ejecución

```yaml
kiro:review:
  stage: review
  image: node:20-bookworm-slim
  timeout: 15m
  interruptible: true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - terraform/**/*
    - if: '$CI_PIPELINE_SOURCE == "web" && $REVIEW_MODE'
```

Tres cosas a destacar:

*   `changes: terraform/**/*` es el equivalente a `paths:` de GitHub: el review solo corre cuando el MR toca infraestructura.
    
*   `interruptible: true` reemplaza al `concurrency: cancel-in-progress` de GitHub. Si pusheás de nuevo al mismo MR, el run anterior se cancela. Ahorra créditos de Kiro y evita notes duplicadas.
    
*   El segundo `rule` habilita el **review manual on-demand** desde *Run pipeline*, pasando la variable `REVIEW_MODE` (`focused` o `full`).
    

### 3\. Ejecución de Kiro CLI

El paso clave es la invocación de `kiro-cli chat` con el agente `code-reviewer`:

```bash
kiro-cli chat \
  --no-interactive \
  --agent code-reviewer \
  --trust-all-tools \
  "Realiza una revisión completa de seguridad, calidad y buenas prácticas
   de infraestructura sobre el directorio terraform/..."
```

Los flags importantes:

| Flag | Qué hace |
| --- | --- |
| `--no-interactive` | Modo headless, sin esperar input del usuario |
| `--agent code-reviewer` | Usa el agente especializado en review de código |
| `--trust-all-tools` | Permite al agente usar todas las herramientas disponibles |

El prompt le pide a Kiro que lea todos los archivos `.tf` (no solo los modificados), analice seguridad, IAM, secretos, red, cifrado, tags y costos, organice los hallazgos por severidad (Crítico / Alto / Medio / Bajo), incluya archivo afectado, líneas, impacto y fix accionable, y genere un resumen ejecutivo con priorización.

### 4\. Agentes custom: que el warning no te agarre desprevenido

Acá hay un detalle que en GitHub a veces pasa inadvertido: los agentes `code-reviewer` y `doc-generator` **no existen por defecto en Kiro**. Son agentes custom que se definen con archivos JSON. Si no los tenés, Kiro tira:

```plaintext
Error: no agent with name code-reviewer found. Falling back to user specified default
```

…y corre con el agente genérico. Funciona, pero perdés el comportamiento afinado. Por eso los definí como **agentes locales** en `.kiro/agents/`, que Kiro descubre automáticamente al correr desde la raíz del repo:

| Agente | Rol | Permisos |
| --- | --- | --- |
| `code-reviewer` | Audita Terraform | Read-only sobre el código; solo escribe `kiro-findings.json` |
| `doc-generator` | Genera docs | Escribe solo en `docs/**` y `README.md` |

El `code-reviewer` es read-only por diseño: puede leer todos los `.tf` y correr comandos de solo lectura, pero tiene `terraform apply`, `git push` y `rm` explícitamente denegados. Un reviewer no debería poder tocar nada.

> Otro warning benigno que vas a ver en CI: `Failed to retrieve MCP settings; MCP functionality disabled`. Ignoralo. En modo headless con API key no hay perfil de usuario ni servidores MCP configurados, y el review no los necesita — usa las herramientas nativas de filesystem.

### 5\. Limpieza de salida

La salida cruda de `kiro-cli` incluye secuencias ANSI, spinners, logs internos y fences de código rotos. El script `clean-kiro-output.js` la transforma en Markdown limpio en 7 fases:

| Fase | Descripción |
| --- | --- |
| 1 — ANSI | Elimina secuencias de escape, spinners, box-drawing |
| 2 — Content start | Detecta dónde empieza el contenido real |
| 3 — Noise | Remueve logs de herramientas y meta-comentarios del agente |
| 4 — Loose fences | Convierte marcadores de lenguaje sueltos en code fences |
| 5 — Fence repair | Fusiona bloques HCL partidos, cierra fences huérfanos |
| 6 — Cleanup | Normaliza saltos de línea, asegura paridad de fences |
| 7 — Write | Escribe el resultado final |

Cada fase está envuelta en `try/catch` individual — si una falla, las demás siguen ejecutándose.

### 6\. Note en el MR

Acá está la diferencia técnica más grande con GitHub. En GitHub usás `actions/github-script` para postear el comentario. En GitLab no hay marketplace de actions, así que escribimos `post-mr-note.js`, que usa la **API REST de GitLab** (solo el módulo `https` nativo de Node, cero dependencias).

El script:

1.  Lee el Markdown limpio.
    
2.  Busca si ya existe una note previa del bot (por un marcador `## 🤖 Kiro Code Review`).
    
3.  Si existe, la actualiza in-place (`PUT`). Si no, la crea (`POST`).
    

Esto elimina el spam de notes cuando iterás sobre el mismo MR.

Usa las variables predefinidas de GitLab `CI_API_V4_URL`, `CI_PROJECT_ID` y `CI_MERGE_REQUEST_IID`, más un token de API.

> ⚠️ **Detalle clave de auth**: el `CI_JOB_TOKEN` que GitLab inyecta en cada job **no puede escribir notes en MRs ni pushear ramas**. Por eso se necesita un **Project Access Token** con scope `api` (y `write_repository` para el job de docs), guardado como variable CI/CD `GITLAB_API_TOKEN`.

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/eec99d3f-3431-4dfb-a26d-e073a87e898f.png align="center")

* * *

## Resultados en acción

Esto es lo que Kiro genera automáticamente en cada MR. Cada hallazgo incluye título claro del problema, archivo y líneas afectadas, explicación de por qué es un problema, impacto real en la infraestructura, y fix accionable con ejemplo de código corregido.

Al final del review, Kiro genera un resumen con la severidad y cantidad de hallazgos, más una priorización recomendada. Y el issue Dashboard queda como punto único de verdad para trackear el progreso de los fixes.

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/d981b638-9666-4d15-a7eb-99a942d5e2aa.png align="center")

* * *

## Bonus: documentación automática con Kiro

El repo incluye un segundo job (`kiro:docs`) que demuestra otra capacidad de Kiro headless: generación automática de documentación.

Cómo funciona:

*   Se dispara con cada push a la rama default que modifique código fuente (ignora `docs/`, `*.md`, `.gitlab/**` y `.gitlab-ci.yml`).
    
*   Kiro escanea el repo completo con el agente `doc-generator`.
    
*   Actualiza `docs/` y `README.md` según el estado real del código.
    
*   Si hay cambios, los pushea a una rama `docs/auto-update-<timestamp>`.
    

```yaml
kiro:docs:
  stage: docs
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      changes:
        - "**/*"
        - "!docs/**/*"
        - "!**/*.md"
        - "!.gitlab/**/*"
        - "!.gitlab-ci.yml"
```

El push de la rama de docs usa la URL autenticada con `oauth2`:

```bash
git push -o ci.skip \
  "https://oauth2:${GITLAB_API_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" \
  "HEAD:${BRANCH}"
```

Dos detalles que aprendí a los golpes: el username canónico para pushear con un Access Token es `oauth2` (un username arbitrario te da `HTTP Basic: Access denied`), y el flag `-o ci.skip` evita que el push dispare un pipeline en cadena.

Esto muestra que Kiro no es solo para reviews puede ser parte activa del mantenimiento de un proyecto, manteniendo la documentación al día como parte del flujo CI/CD.

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/af719ba6-6f62-449e-b123-bc917fa69ff1.png align="center")

* * *

## Setup: cómo replicar esto en tu repo

### Requisitos previos

*   Cuenta de GitLab con acceso a CI/CD.
    
*   API key de Kiro (`kiro.dev/settings/api`), requiere plan Pro, Pro+ o Power.
    

### Paso a paso

**1\. Fork / importá el repo**

```plaintext
https://gitlab.com/roxsross/kiro-iac-review
```

**2\. Creá un Project Access Token**

`Settings → Access tokens → Add new token`

*   Role: `Developer` (o superior)
    
*   Scopes: `api` + `write_repository`
    

**3\. Configurá las variables CI/CD**

`Settings → CI/CD → Variables → Add variable`:

| Variable | Masked | Descripción |
| --- | --- | --- |
| `KIRO_API_KEY` | ✅ | API key de Kiro para auth headless |
| `GITLAB_API_TOKEN` | ✅ | El Project Access Token del paso 2 |

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/957cb1a3-0ad2-4c94-81d4-f74cf66e26ce.png align="center")

**4\. Abrí un MR que toque Terraform**

```bash
git checkout -b feat/test-review
# Hacé un cambio en terraform/main.tf
git add terraform/main.tf
git commit -m "feat(terraform): test kiro review"
git push -u origin feat/test-review \
  -o merge_request.create \
  -o merge_request.target=main
```

**5\. Esperá ~60 segundos**

La note de Kiro aparece automáticamente en el MR, y el issue Dashboard se crea/actualiza.

### Protección de la rama default recomendada

Para que el review sea enforzable:

`Settings → Repository → Protected branches` → rama `main`, con push restringido (solo vía MR).

Y en `Settings → Merge requests`:

*   ✅ Pipelines must succeed
    
*   ✅ All threads must be resolved
    
*   ✅ Squash commits when merging
    

* * *

## Tabla de equivalencias GitHub → GitLab

Si ya conocías la versión GitHub, esto te ubica rápido:

| GitHub Actions | GitLab CI/CD |
| --- | --- |
| `.github/workflows/kiro-review.yml` | job `kiro:review` en `.gitlab-ci.yml` |
| `.github/workflows/kiro-docs.yml` | job `kiro:docs` en `.gitlab-ci.yml` |
| `on: pull_request` (paths) | `rules: merge_request_event + changes:` |
| `on: workflow_dispatch` | `rules: $CI_PIPELINE_SOURCE == "web"` + var |
| `concurrency: cancel-in-progress` | `interruptible: true` |
| `secrets.GITHUB_TOKEN` | `GITLAB_API_TOKEN` (Project Access Token) |
| `actions/github-script` (PR comment) | `post-mr-note.js` (API REST de GitLab) |
| `github.event.pull_request.number` | `CI_MERGE_REQUEST_IID` |
| Pull Request | Merge Request |
| Comentario en PR | Note en MR + issue Dashboard |

* * *

## Lecciones aprendidas

### Lo que funcionó bien

*   **El prompt es todo.** La calidad del review depende directamente del prompt que le das a Kiro. Ser explícito sobre qué analizar, en qué formato reportar y qué priorizar hace una diferencia enorme.
    
*   **La limpieza de salida es necesaria.** La salida cruda de `kiro-cli` incluye ruido del CLI que rompe el Markdown. El script de 7 fases resilientes lo resuelve sin fragilidad.
    
*   **Note update > note create.** Actualizar la misma note en vez de crear una nueva mantiene el MR limpio cuando iterás. El mismo principio aplica al issue Dashboard.
    
*   `interruptible` **ahorra créditos.** Clave cuando el equipo pushea seguido al mismo MR.
    

### Lo que hay que tener en cuenta

*   **Auth de GitLab**: el `CI_JOB_TOKEN` no alcanza para escribir notes ni pushear. Necesitás un Access Token con scope `api`. Y para pushear, username `oauth2`.
    
*   **Créditos**: cada run consume créditos de Kiro. Usá `changes:` con paths específicos para evitar runs innecesarios.
    
*   **Timeout**: 15 minutos alcanza para repos chicos. Para repos grandes, ajustá.
    
*   **El JSON de findings puede fallar**: los LLMs a veces se desvían del formato pedido. El script es tolerante, y si el JSON sale roto, la note del MR igual se postea — no se cae el pipeline.
    
*   **El review no reemplaza al humano**: Kiro encuentra problemas técnicos, pero las decisiones de arquitectura y los trade-offs siguen siendo humanos.
    

* * *

## Conclusión

Lo que construimos acá es simple en concepto pero poderoso en práctica: un pipeline que piensa antes de deployar. Kiro headless mode corre dentro de GitLab CI/CD, revisa cada MR de Terraform de forma automática, postea una note estructurada con hallazgos accionables, y mantiene un Dashboard de issues que se va limpiando a medida que arreglás la infra.

No reemplaza la revisión humana — la potencia. Te da un primer pase automatizado que atrapa los problemas obvios (y no tan obvios) antes de que lleguen a producción. Y como bonus, puede mantener tu documentación actualizada sin esfuerzo extra.

El código está disponible para que lo forkees, lo pruebes y lo adaptes:

[**https://gitlab.com/roxsross/kiro-iac-review**](https://gitlab.com/roxsross/kiro-iac-review)

![](https://cdn.hashnode.com/uploads/covers/620c453d7cf10d2765e28b6c/128a3197-f641-4b29-8f8a-1eda72f97fc6.png align="center")

* * *

## Referencias

*   [Kiro CLI 2.0: headless mode, Windows y nueva UX](https://kiro.dev/blog/kiro-cli-2)
    
*   [Kiro headless mode — documentación](https://kiro.dev/docs/cli/headless)
    
*   [Ejemplo práctico de CLI headless](https://kiro.dev/blog/headless-mode-example)
    
*   [Kiro steering files](https://kiro.dev/docs/cli/steering)
    

* * *

**🔥 Build with fire, deploy with power 🔥**

[@roxsross](https://roxs.dev) · roxs.dev
