Qué vas a tener
Un mensaje en Telegram (chat privado o grupo) cuando un job de Actions falla: repo, rama y enlace al run.
No hace falta servidor propio. Bastan un bot de Telegram, dos secretos guardados en GitHub y unas líneas más en tu configuración de GitHub Actions.
- Falla un job en Actions
- Un step llama a sendMessage
- Telegram muestra el aviso
Si aún no tienes claro el bot/token/chat_id, lee Avisos con un bot de Telegram.
Requisitos
- Un repo en GitHub. No hace falta tener ya workflows: aquí creas el primero
- Cuenta de Telegram — el bot se crea en el propio chat, en un par de minutos
- Permiso para añadir secrets en ese repo, o alguien que te los cree
Crea el bot
- En Telegram, abre @BotFather (se abre en una pestaña nueva).
/newbot→ nombre y un username que acabe enbot.- Copia el token.
- BotFather te da un enlace
t.me/…: ábrelo, pulsa Start (o escribehola). Si usas un grupo, añade el bot y dale permiso de enviar; eliddel grupo suele ser negativo.
Obtén el chat_id
Pega el token de BotFather donde dice pega-aqui-tu-token. Elige tu sistema en el bloque.
TOKEN="pega-aqui-tu-token"
curl -sS "https://api.telegram.org/bot${TOKEN}/getUpdates"Busca un trozo como este (tus números serán distintos):
"chat": {
"id": 123456789,
"type": "private"
}Ese "id" es el chat_id. Si result viene vacío, manda otro mensaje al bot y reintenta. En PowerShell, con $r de arriba, también puedes mirar $r.result.message.chat.id (o el primer elemento de result si viene como lista).
Puedes comprobar el destino antes de tocar Actions (sustituye también 123456789 por tu chat_id):
TOKEN="pega-aqui-tu-token"
CHAT_ID="123456789"
curl -sS -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "{\"chat_id\":${CHAT_ID},\"text\":\"Prueba desde la integración\"}"A partir de aquí desaparecen las pestañas de sistema: los workflows corren en la máquina de GitHub, que es Linux, así que los comandos son siempre los mismos aunque tú estés en Windows.
Guárdalos en GitHub Secrets
En el repo: Settings → Secrets and variables → Actions:
| Nombre | Valor |
|---|---|
TELEGRAM_BOT_TOKEN | Token de BotFather |
TELEGRAM_CHAT_ID | El chat_id que sacaste |
Si los nombres no coinciden, el workflow de abajo no los verá.
Añade el workflow
Crea .github/workflows/telegram-on-failure.yml:
name: Aviso Telegram si falla CI
on:
workflow_dispatch:
jobs:
# Lo dejamos fallando a propósito para probar el aviso.
# Luego lo cambias por tu job real.
fail-on-purpose:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Forzar fallo (solo para probar)
run: exit 1
notify-telegram:
needs: fail-on-purpose
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Telegram
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "Faltan TELEGRAM_BOT_TOKEN o TELEGRAM_CHAT_ID."
exit 1
fi
text=$(printf 'GitHub Actions falló\n%s · rama %s\n%s/%s/actions/runs/%s' \
"${{ github.repository }}" \
"${{ github.ref_name }}" \
"${{ github.server_url }}" \
"${{ github.repository }}" \
"${{ github.run_id }}")
payload=$(jq -n \
--arg chat "$TELEGRAM_CHAT_ID" \
--arg text "$text" \
'{chat_id: $chat, text: $text}')
curl -sS -X POST \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "$payload"Lo que merece atención de ese bloque:
| Pieza | Para qué |
|---|---|
needs: + if: failure() | La pareja que hace el truco: needs dice a qué job espera y if: failure() que solo se ejecute si aquel falló. Sin esto avisaría siempre |
| Los dos secretos | Token y destino. Nunca se escriben en el archivo: el YAML se ve en el repo |
jq + curl | Arman el mensaje y llaman a sendMessage. Vienen instalados en la máquina de GitHub, no hay que añadir nada |
Pruébalo
- Crea el archivo en el repo: Add file → Create new file → ruta `.github/workflows/telegram-on-failure.yml` → pega el YAML → Commit (o súbelo con git).
- En Actions, abre Aviso Telegram si falla CI → Run workflow.
- Espera a que fail-on-purpose falle (es normal) y a que notify-telegram termine en verde.
- Mira Telegram: mensaje con repo, rama y enlace.
Adáptalo a tu CI
Cuando el aviso de prueba te funcione, deja de usar fail-on-purpose. Abajo tienes YAML listo: adapta el comando del job; los secrets siguen siendo los mismos.
Un job (lo habitual)
name: Aviso Telegram si falla CI
on:
workflow_dispatch:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
notify-telegram:
needs: test
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Telegram
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "Faltan TELEGRAM_BOT_TOKEN o TELEGRAM_CHAT_ID."
exit 1
fi
text=$(printf 'GitHub Actions falló\n%s · rama %s\n%s/%s/actions/runs/%s' \
"${{ github.repository }}" \
"${{ github.ref_name }}" \
"${{ github.server_url }}" \
"${{ github.repository }}" \
"${{ github.run_id }}")
payload=$(jq -n \
--arg chat "$TELEGRAM_CHAT_ID" \
--arg text "$text" \
'{chat_id: $chat, text: $text}')
curl -sS -X POST \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "$payload"needs: test = espera a test.
if: failure() = solo corre si test falló.
Si test pasa en verde, Telegram no recibe nada.
Varios jobs
El aviso espera a varios jobs y salta si falla cualquiera:
name: Aviso Telegram si falla CI
on:
workflow_dispatch:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
notify-telegram:
needs: [test, build, lint]
if: failure()
runs-on: ubuntu-latest
steps:
- name: Avisar en Telegram
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
echo "Faltan TELEGRAM_BOT_TOKEN o TELEGRAM_CHAT_ID."
exit 1
fi
text=$(printf 'GitHub Actions falló\n%s · rama %s\n%s/%s/actions/runs/%s' \
"${{ github.repository }}" \
"${{ github.ref_name }}" \
"${{ github.server_url }}" \
"${{ github.repository }}" \
"${{ github.run_id }}")
payload=$(jq -n \
--arg chat "$TELEGRAM_CHAT_ID" \
--arg text "$text" \
'{chat_id: $chat, text: $text}')
curl -sS -X POST \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-H "Content-Type: application/json" \
-d "$payload"Cuándo se dispara
No es otro flujo: es el bloque on: del mismo archivo.
Qué pones en on: | Efecto |
|---|---|
Solo workflow_dispatch | Lo lanzas a mano (como en la prueba) |
Solo push a main | Corre solo al hacer push a main |
| Los dos | Puedes probar a mano y recibir avisos reales en main |
Los ejemplos de “un job” / “varios jobs” ya llevan los dos. Si aún usas el YAML de prueba (solo manual), añade el push cuando el mensaje te convenza.
Texto plano
Los ejemplos usan texto plano (sin parse_mode). Es lo más seguro con nombres de repo/rama que llevan _ o /. Si más adelante usas HTML o MarkdownV2, escapa lo que venga de GitHub.
Seguridad
- Un bot dedicado a CI reduce el radio de daño.
- Este flujo no verifica firmas de GitHub: quien tenga el token puede escribir al
chat_id. - Evita spamear: un mensaje por fallo de job suele bastar.
Errores habituales
| Qué ves | Causa habitual | Qué hacer |
|---|---|---|
Sin chat_id en getUpdates | No diste Start al bot | Abre el chat, Start, reintenta |
Unauthorized | Token mal pegado | Reescribe el secret |
chat not found | chat_id de otro chat o bot | Vuelve a sacar el id con getUpdates |
notify-telegram no corre | El job anterior no falló | Revisa needs + if: failure() |
| Actions en verde y silencio en Telegram | Miras otro chat, o el job avisado no falló | Confirma chat_id y el grafo del run |
| Mensaje duplicado | Reejecutaste el workflow | Normal aquí; no hay deduplicación |
| 400 de Telegram | JSON mal formado o parse_mode sin escapar | Usa el bloque del ejemplo con jq y texto plano |
Alternativas
- Incoming Webhook de otro chat
- Mismo aviso de CI, otro destino (p. ej. Discord o Slack).
- Email de GitHub
- Cero setup; más fácil de ignorar.
- Grupo de Telegram vs chat privado
- Grupo para el equipo; privado para avisos solo tuyos.