Cómo alojar OpenTag para menciones @agent
Instala OpenTag v0.9.0 en un VPS para enviar menciones de Slack y GitHub a tu agente, con TLS, firmas de webhook, permisos de tokens y valores seguros.
Qué hace OpenTag cuando mencionas a un agente
OpenTag convierte una mención con @ en un hilo de Slack o en una incidencia de GitHub en una ejecución de un agente de programación en una máquina que administras. Alguien comenta @opentag investigate this en una incidencia. Un listener recibe el evento de la plataforma, comprueba su firma, relaciona la mención con un proyecto asociado, inicia un agente de programación sobre un checkout local y publica el resultado en el mismo hilo.
El proyecto usa la licencia MIT y está disponible en amplifthq/opentag. En agosto de 2026, la versión etiquetada más reciente es v0.9.0, publicada el 28 de julio de 2026, y se distribuye como paquete npm. No existe una imagen de contenedor oficial, por lo que debes fijar la versión de npm. Todos los comandos siguientes la fijan.
Esto se convierte en un proyecto para un VPS en lugar de un proyecto para un portátil por la parte de GitHub. GitHub entrega los eventos del repositorio mediante una solicitud HTTP a una URL que registras una sola vez, por lo que esa URL debe responder en la misma dirección mañana.
Las cuatro piezas móviles
El listener recibe los eventos de la plataforma, y cada plataforma tiene el suyo. El listener de GitHub es un endpoint HTTP en el puerto 3050 y la ruta /github/webhooks. El listener de Slack Events API está en el puerto 3040 y en /slack/events. Slack también puede ejecutarse en Socket Mode. En ese modo, la aplicación abre un WebSocket saliente y no necesita ningún puerto entrante.
El dispatcher es el coordinador. Escucha en el puerto 3030 de forma predeterminada, guarda el estado de las ejecuciones en el archivo de base de datos local definido por OPENTAG_DATABASE_PATH y registra un seguimiento de auditoría para cada ejecución. Nada externo debe poder acceder nunca a este puerto.
El runner es el daemon local. Consulta si hay trabajo, reclama una ejecución, mantiene un lease sobre ella y envía un heartbeat cada 15 segundos de forma predeterminada mientras la ejecución está activa. Rechaza cualquier ejecución reclamada cuyo destino de proyecto falte o esté fuera de la allowlist de su propia configuración. Esta comprobación impide que un evento de GitHub dirija el agente a un repositorio que nunca vinculó.
El executor es el agente de programación. OpenTag lo inicia mediante ACP (agent client protocol), un protocolo JSON-RPC que usa la entrada y la salida estándar. Así, el agente se ejecuta como proceso hijo dentro de un directorio de trabajo que OpenTag le proporciona. Los nombres integrados incluyen echo, codex, claude-code, cursor, opencode, hermes y openclaw. Empiece con echo, el executor que incluye la configuración de ejemplo, porque permite comprobar que todo el flujo funciona antes de que un modelo modifique su código.
El orden nunca cambia: evento de la plataforma, comprobación de la firma, registro de la ejecución, reclamación, agente y respuesta en el hilo.
Por qué un portátil y un túnel no son suficientes
La guía de configuración de GitHub indica ejecutar ngrok http 3050 y pegar el host del túnel en el webhook del repositorio. Esto funciona durante los primeros diez minutos. La dirección de un túnel gratuito cambia cada vez que se reinicia el proceso y deja de existir cuando el portátil entra en suspensión. GitHub conserva la URL de payload anterior y sigue intentando usarla, por lo que la pestaña Recent Deliveries de la configuración del webhook se llena de errores mientras el hilo permanece en silencio. Nadie lo detecta durante una semana, porque un webhook que no hace nada parece exactamente un bot que nadie mencionó.
Un VPS resuelve los dos problemas. El nombre DNS no cambia, por lo que la URL de payload que se pega una vez sigue siendo válida. La máquina no entra en suspensión, por lo que un comentario publicado a las 02:00 recibe una respuesta. Configure correctamente el equipo antes de continuar: los primeros diez minutos en un VPS nuevo cubre el usuario de inicio de sesión y el firewall que presupone esta guía.
Slack es la excepción. En Socket Mode, la conexión se inicia hacia el exterior y no necesita una URL pública, por lo que una implementación exclusiva para Slack puede permanecer cerrada. GitHub no tiene un equivalente. Los webhooks de repositorio son solicitudes HTTP entrantes. Esto exige un endpoint público, TLS (transport layer security) y una comprobación de firma.
Autoalojar OpenTag en Ubuntu desde una versión fijada
OpenTag v0.9.0 requiere Node.js 22 o posterior. Ubuntu 24.04 incluye Node 18 en su propio repositorio, por lo que debe instalarlo desde NodeSource.
curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs
node -vnode -v debe mostrar v22 o una versión posterior. En Node 20, la instalación muestra una advertencia EBADENGINE y la CLI puede fallar al iniciarse.
Asigne al servicio su propia cuenta. El agente se ejecuta con los permisos de ese usuario, por lo que no debe ser su cuenta de inicio de sesión ni root. Usuarios con privilegios mínimos en un VPS explica por qué esta separación justifica el paso adicional.
sudo adduser --disabled-password --gecos "" opentag
sudo loginctl enable-linger opentag
sudo npm install -g @opentag/cli@0.9.0
command -v opentagcommand -v opentag debe mostrar una ruta como /usr/bin/opentag. La configuración de linger es importante en Linux: OpenTag instala su servicio en segundo plano mediante systemd y un servicio de usuario sin linger se detiene en cuanto se cierra la sesión SSH.
Ejecute la configuración con ese usuario.
sudo -iu opentag opentag setupLa configuración solicita seis datos: el idioma de la CLI, la dirección de escucha local, el agente de programación, el proyecto local en el que se trabajará, las credenciales de la plataforma que se guardarán y el modo de ejecución. Mantenga la dirección de escucha en 127.0.0.1, porque nginx termina TLS y reenvía las conexiones a esa dirección. Por tanto, los listeners no necesitan ser accesibles desde el exterior. Para GitHub también solicita el repositorio en formato owner/repo, si puede abrir pull requests, el puerto del webhook (3050 de forma predeterminada) y el token. Al final, elija el modo de servicio en segundo plano. Si ya tiene una configuración y quiere instalar el servicio sin indicaciones, opentag setup --service lo hace.
La configuración se guarda en /home/opentag/.config/opentag/config.json y el estado de ejecución en /home/opentag/.local/state/opentag. Conviene revisar manualmente estas claves después de que la configuración escriba el archivo.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}Prefiera runnerToken, el bearer token limitado al runner, frente al antiguo pairingToken compartido. El archivo de configuración guarda las credenciales en texto plano, salvo que las sustituya por una referencia a un secreto. Esta referencia obtiene el valor del entorno o de un archivo del disco al iniciar. En cualquier caso, este archivo es el elemento más sensible del servidor: debe tener el modo 600, pertenecer a opentag y no estar nunca dentro de un repositorio git. La explicación general está en mantener los secretos fuera de los agentes de IA.
Compruebe la instalación antes de exponerla.
sudo -iu opentag opentag doctor
sudo -iu opentag opentag statusopentag doctor comprueba el dispatcher, los bindings, los checkouts y los ejecutores. opentag status muestra la configuración y el estado de ejecución, y puede limitarse a una única ejecución cuando ya existan ejecuciones. Corrija todo lo que informe doctor antes de apuntar una plataforma a este servidor.
Coloque TLS delante y abra sólo dos rutas
nginx termina TLS y reenvía exactamente dos rutas. Todo lo demás devuelve 404, por lo que un escáner que encuentre el host no obtiene información sobre lo que se ejecuta detrás.
Escriba un bloque de servidor sencillo para el puerto 80 en /etc/nginx/sites-available/opentag con las dos ubicaciones siguientes y deje que Certbot añada la parte TLS.
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/opentag /etc/nginx/sites-enabled/opentag
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d opentag.example.comnginx -t muestra syntax is ok y test is successful. Es el único elemento entre un error tipográfico y una recarga que deja el sitio fuera de servicio. Certbot en Ubuntu 24.04 con nginx explica la renovación y las formas en que puede fallar un desafío ACME (entorno de gestión automática de certificados). El bloque terminado es el siguiente.
server {
listen 443 ssl;
server_name opentag.example.com;
ssl_certificate /etc/letsencrypt/live/opentag.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/opentag.example.com/privkey.pem;
client_max_body_size 2m;
location = /github/webhooks {
proxy_pass http://127.0.0.1:3050;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
location = /slack/events {
proxy_pass http://127.0.0.1:3040;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
location / {
return 404;
}
}El = de location = /github/webhooks es una coincidencia exacta, y proxy_pass sin nada después del puerto pasa la URI original sin modificar. Si elimina =, también se reenvía cada ruta bajo /github/webhooks/, lo que expone más superficie de la que necesita el listener.
El firewall debe mantenerse restringido.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw statusLos puertos 3030, 3040 y 3050 nunca se abren. Confirme que estén asociados a loopback y no a todas las interfaces.
sudo ss -tlnpTodas las líneas de OpenTag deben mostrar 127.0.0.1:3030 o algo similar. Una línea que muestre 0.0.0.0:3050 significa que el listener se ofrece a toda Internet y que sólo ufw lo está bloqueando. Un error en el firewall bastaría para activar un agente expuesto. conceptos básicos del firewall ufw explica qué hace realmente esa denegación predeterminada.
Dos comprobaciones confirman la puerta de entrada. curl -I https://opentag.example.com/ devuelve 404 desde nginx, lo que demuestra que el certificado es válido y que el bloque catch-all está cerrado. Una petición a /slack/events o /github/webhooks sin firma nunca debe devolver 200.
Verifique todas las firmas, porque la URL es pública
Cualquiera puede encontrar la URL de carga útil. Está en la configuración del repositorio, en el historial del navegador o en una captura de pantalla pegada en un ticket. La firma es lo único que distingue una entrega real de GitHub de una petición escrita manualmente por otra persona.
GitHub firma cada entrega con el secreto del webhook y envía el resultado en la cabecera x-hub-signature-256. OpenTag verifica esa cabecera con platforms.github.webhookSecret. Las notas de endurecimiento del proyecto establecen la regla directamente: no acepte eventos de origen sin firma en /github/webhooks. Slack firma cada petición con SLACK_SIGNING_SECRET e incluye una marca de tiempo, por lo que un cuerpo capturado no se puede reproducir varias horas después.
Omitir esta comprobación no supone un riesgo menor. Un endpoint sin verificar acepta una carga útil issue_comment escrita manualmente que contiene @opentag. OpenTag ejecuta entonces un agente de programación, con su token, en su checkout y siguiendo instrucciones de un desconocido. La respuesta se envía al hilo que indique la carga útil falsa.
OpenTag añade dos capas más. Las entregas de origen se registran mediante el ID de entrega, por lo que volver a entregar el mismo evento no inicia una segunda ejecución. Las llamadas del runner aceptan claves de idempotencia, por lo que repetir una llamada devuelve éxito sin añadir otro evento de auditoría.
Los límites de tasa se pueden configurar y deben estar activados. OPENTAG_RATE_LIMIT_WINDOW_MS y OPENTAG_RATE_LIMIT_MAX_REQUESTS limitan la tasa de peticiones, OPENTAG_MAX_REQUEST_BODY_BYTES limita el tamaño del cuerpo y las cargas útiles demasiado grandes se rechazan con 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true existe para el desarrollo local y no debe utilizarse en un servidor público. Otra regla de las mismas notas: una URL de relay pública debe usar HTTPS, y la CLI sólo permite HTTP sin cifrar para localhost.
¿Qué ámbitos de token necesita realmente el bot?
En GitHub, OpenTag usa un token de acceso personal de permisos específicos en lugar de una GitHub App. La documentación indica que la opción de la App está prevista y que hoy no es la configuración predeterminada de la CLI. Esto tiene una consecuencia que suele pasarse por alto: el bot publica comentarios como la persona que creó el token. Créelo con una cuenta cuyo nombre esté dispuesto a ver citado en cada respuesta de triaje.
Limite los permisos exactamente como indica la guía de configuración. Seleccione Only select repositories y elija uno. Conceda Issues: Read and write y Pull requests: Read and write. Esto basta para leer una mención y responder en el hilo.
Observe lo que falta: acceso de escritura al código. OpenTag no publica ramas a menos que preparePullRequestBranch esté establecido en true, y existe un githubApplyToken independiente para que el token que escribe código no sea el mismo que publica comentarios. Manténgalos separados y no active el token de escritura hasta que el flujo de lectura y comentarios haya funcionado durante varias semanas.
La configuración que debe evitar es un token con Contents: Read and write en All repositories. Cualquier persona que pueda comentar en alguno de esos repositorios puede dirigir ahora un agente con permisos para crear commits, y el registro de auditoría atribuirá la acción al propietario del token. Amplíe el ámbito a un repositorio cada vez, después de que el agente haya demostrado que lo merece.
En Slack, los ámbitos del bot son app_mentions:read, chat:write, reactions:write y channels:history. Los canales privados también necesitan groups:history y una suscripción al evento message.groups. Socket Mode necesita un token de nivel de aplicación con connections:write, cuyo valor empieza por xapp-. channels:history lee el historial de mensajes de los canales públicos a los que se ha añadido el bot. Por tanto, añada el bot sólo a los canales donde se necesite, no a todos.
Enrutar un problema de principio a fin
El webhook es el primer paso. En el repositorio, abra Settings, después Webhooks y, por último, Add webhook. La URL de carga útil es https://opentag.example.com/github/webhooks, el tipo de contenido es application/json y el secreto es el que generó la configuración. Suscríbase a Issue comments y Pull request review comments, y a ningún otro evento.
GitHub envía una entrega de prueba en cuanto guarda la configuración. Abra Recent Deliveries y compruebe que la solicitud llegó al servidor. Un 502 indica que nginx no pudo llegar al proceso que escucha, por lo que el problema es local y no de GitHub.
Ahora úselo. Abra una incidencia que describa un error y escriba este comentario:
@opentag triage this. Reproduce the report against the current main branch, then reply with the file and function most likely responsible, plus the test you would write first.Esto es lo que debe ocurrir, en orden. Recent Deliveries registra la entrega issue_comment con una respuesta 2xx. El dispatcher registra una ejecución. El runner la reclama y empieza a enviar señales de actividad. El executor abre el checkout y trabaja. La respuesta llega como comentario en el mismo hilo de la incidencia. sudo -iu opentag opentag status muestra la ejecución mientras está en curso, para que pueda supervisarla en lugar de hacer suposiciones.
Establezca approvalMode en ask antes de la primera ejecución real. En el modo ask, la ejecución se pausa y espera a que una persona la autorice antes de realizar cualquier cambio de estado. Los modos auto y autonomous también están disponibles y son opciones razonables más adelante, en un repositorio cuyos registros de conversaciones haya revisado durante un mes.
En Slack, la misma ejecución comienza con /bind owner/repo en el canal y, después, con una mención. El bot también responde a /help, /status, /doctor, /stop y /unbind confirm. Restrinja quién puede modificar las vinculaciones con OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, una lista de Slack user IDs separada por comas, porque una vinculación es el mapeo entre un canal público y un checkout en su servidor.
Triage es una buena primera opción porque lee datos y no escribe en ellos, y la respuesta es fácil de evaluar. Review es el siguiente paso, donde el agente comenta un diff en lugar de una incidencia: un agente de revisión de pull requests autoalojado usa esta misma arquitectura orientada a pull requests. Si quiere que el agente acceda a sus propios sistemas mientras trabaja, esa es la función de los servidores MCP en un VPS. La búsqueda web es la otra capacidad que Triage suele necesitar, y conectar el agente a su propia instancia de SearXNG mantiene esas consultas en hardware que usted administra, a cambio de añadir otro canal por el que el texto de un tercero llega al agente.
¿Qué ocurre cuando el agente se equivoca delante de todos?
Se equivocará. La cuestión es cuánto cuesta.
Una respuesta incorrecta en una incidencia pública es un comentario publicado con un nombre que el equipo reconoce, y GitHub envía un correo a todas las personas suscritas en cuanto se publica. Eliminar el comentario no retira el correo. Lo mismo ocurre con una notificación de Slack. Planifique para que la respuesta sea incorrecta en público, no para que sea correcta en privado.
Cuatro decisiones limitan el daño y son más importantes que cualquier prompt que escriba.
- Ejecútelo en modo
ask, para que el agente proponga, una persona apruebe y un plan incorrecto cueste un clic. - Mantenga
preparePullRequestBranchcon su valor predeterminado de false, para que el peor resultado de una ejecución incorrecta sea un comentario equivocado y no una rama incorrecta. - Vincule un repositorio y un canal al principio. El runner rechaza cualquier ejecución cuyo destino de proyecto esté fuera de su allowlist local, por lo que un repositorio no vinculado no puede activar el agente por sí mismo.
- Mantenga separado el token de comentarios de cualquier token de aplicación, para que revocar el acceso de escritura no interrumpa el triage.
Slack tiene un comando /stop para una ejecución que avanza en la dirección equivocada. Cada ejecución también deja un registro de auditoría con la mención que la inició y las acciones del agente. Ese registro es lo que se revisa después para determinar dónde se produjo el error.
La parte social es tan importante como la configuración. Coloque el bot en un canal donde las personas esperen interactuar con una máquina y sepan que puede equivocarse. Una respuesta incorrecta expresada con seguridad en un canal de cuarenta personas que dan por hecho que una persona la revisó cuesta más que el tiempo ahorrado en el triage. Indique en la descripción del canal quién es responsable del bot y quién revisa sus resultados.
Copias de seguridad, actualizaciones y fijación de versión
Dos rutas contienen todo: /home/opentag/.config/opentag/config.json y /home/opentag/.local/state/opentag. La primera contiene las credenciales; la segunda contiene el historial de ejecuciones y el archivo de base de datos. Haga copias de seguridad de ambas con el modo 600 y guárdelas fuera del servidor. Perderlas implica volver a crear los tokens y las vinculaciones, no reconstruir un servidor.
Las actualizaciones consisten en cambiar la versión y reiniciar.
sudo npm install -g @opentag/cli@0.9.0
sudo -iu opentag opentag service stop
sudo -iu opentag opentag service start
sudo -iu opentag opentag doctorFije la versión en lugar de seguir @latest. Este software ejecuta un agente de programación en su repositorio con un token activo, por lo que una versión publicada durante la noche introduce un cambio no revisado en ese entorno. La política de seguridad no incluye correcciones retroportadas, y las correcciones sólo llegan a la versión más reciente. Por tanto, fijar la versión implica leer el registro de cambios y actualizarla de forma intencionada. No significa permanecer para siempre en v0.9.0. El historial hasta julio de 2026 muestra varias versiones al mes, por lo que conviene leer las notas de cada versión antes de cada actualización.
FAQ
¿Necesito un VPS para ejecutar OpenTag o basta con un portátil?
Un portátil basta para Slack, porque Socket Mode abre un WebSocket saliente y no necesita ningún puerto entrante. GitHub funciona de otra forma. Los webhooks del repositorio se entregan mediante HTTP entrante a una URL que se registra una vez. Por tanto, la dirección debe mantenerse igual y responder mientras duerme. La dirección de un host de túnel de una cuenta gratuita cambia en cada reinicio, y GitHub sigue enviando peticiones a la anterior. Esto aparece como entregas fallidas en la pestaña Recent Deliveries del repositorio y como silencio en el hilo. Un VPS con un nombre DNS fijo y un certificado elimina ambos problemas.
¿Qué permisos de GitHub necesita OpenTag?
Un personal access token de permisos detallados limitado a Only select repositories, con Issues: Read and write y Pull requests: Read and write. Esto permite leer una mención y responder en el hilo. No se necesita acceso de escritura al código, salvo que establezca preparePullRequestBranch en true para que OpenTag envíe ramas. También existe un githubApplyToken independiente para mantener separado el token que escribe código del token que publica comentarios. Evite un token para todos los repositorios con permiso contents write, porque cualquier persona que pueda comentar en cualquiera de esos repositorios podría dirigir un agente capaz de hacer commits.
¿Cómo detengo una ejecución que está produciendo resultados incorrectos?
Slack tiene un comando /stop específico para esto. En el servidor, opentag status muestra lo que está en ejecución y opentag service stop detiene el daemon, lo que finaliza toda la canalización en lugar de una sola ejecución. Para no necesitar ninguno de los dos, establezca approvalMode en ask para que las ejecuciones se pausen y requieran la intervención de una persona antes de cambiar nada. Mantenga preparePullRequestBranch en false para que una ejecución incorrecta produzca un comentario en lugar de una rama.
¿Por qué mi webhook devuelve 502 mientras el hilo permanece en silencio?
502 procede de nginx, no de OpenTag, e indica que el proxy no pudo conectarse al listener. /var/log/nginx/error.log mostrará connect() failed (111: Connection refused) while connecting to upstream. El listener está detenido o usa un puerto distinto del que indica la línea proxy_pass. Ejecute sudo ss -tlnp y confirme que hay algo escuchando en 127.0.0.1:3050 para GitHub y en 127.0.0.1:3040 para Slack. Después, ejecute opentag doctor para comprobar los bindings y los ejecutores.