Cómo alojar OpenTag para menciones @ de agentes
Configura OpenTag v0.9.0 en un VPS para Slack y GitHub: TLS, firmas de webhooks, permisos de tokens y valores seguros sin imagen oficial.
Qué hace OpenTag cuando mencionas a un agente
OpenTag convierte una mención con @ en un hilo de Slack o 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 en un checkout local y publica el resultado en el mismo hilo.
El proyecto tiene licencia MIT y se encuentra 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 y no para un portátil debido a 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, en la ruta /github/webhooks. El listener de Slack Events API está en el puerto 3040, 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 un archivo de base de datos local definido por OPENTAG_DATABASE_PATH y registra un historial de auditoría para cada ejecución. Ningún sistema externo debe acceder nunca a este puerto.
El runner es el daemon local. Consulta si hay trabajo, reclama una ejecución, mantiene una concesión sobre ella y envía una señal de actividad 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 lista de permitidos 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. De este modo, 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 incluido en la configuración de ejemplo, porque permite comprobar que todo el flujo funciona antes de que un modelo modifique el 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 que debe 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 gratuita del túnel 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 carga útil antigua 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 del que nadie habló.
Un VPS resuelve los dos problemas que provocan el fallo. El nombre DNS no cambia, por lo que la URL de carga útil que pega una vez sigue siendo válida. La máquina no entra en suspensión, por lo que un comentario a las 02:00 recibe respuesta. Configure primero el servidor correctamente: los primeros diez minutos en un VPS nuevo cubren el usuario de inicio de sesión y el firewall que presupone esta guía.
Slack es la excepción. En Socket Mode establece conexiones salientes 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, lo que requiere un endpoint público y, por tanto, TLS (seguridad de la capa de transporte) y una comprobación de firma.
Autohospedar OpenTag en Ubuntu desde una versión fijada
OpenTag v0.9.0 requiere Node.js 22 o una versión posterior. Ubuntu 24.04 incluye Node 18 en su propio repositorio, así que instale 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. Con Node 20, la instalación muestra una advertencia EBADENGINE y la CLI puede fallar al iniciarse.
Asigne al servicio una cuenta propia. El agente se ejecuta con los permisos de este usuario, por lo que no debe ser su usuario de inicio de sesión ni root. Usuarios con privilegios mínimos en un VPS explica por qué esta separación merece 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 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 nunca tienen que 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 preguntas, 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 comprobar 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 con ámbito del runner, al antiguo pairingToken compartido. El archivo de configuración almacena las credenciales en texto plano, a menos que las sustituya por una referencia a un secreto. Esta referencia lee el valor del entorno o de un archivo del disco al iniciar. En cualquier caso, este archivo es el elemento más sensible del sistema: debe tener permisos 600, pertenecer a opentag y nunca estar dentro de un repositorio git. La explicación más amplia está en mantener los secretos fuera de los agentes de IA.
Compruebe la instalación antes de exponer nada.
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 sola ejecución cuando ya existan ejecuciones. Corrija todo lo que informe doctor antes de apuntar una plataforma a este sistema.
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 la única protección entre un error tipográfico y una recarga que deje el sitio fuera de servicio. Certbot en Ubuntu 24.04 con nginx explica la renovación y los casos en que falla un desafío de ACME (entorno de gestión automática de certificados). El bloque terminado tiene este aspecto.
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 = en location = /github/webhooks es una coincidencia exacta, y proxy_pass sin nada después del puerto pasa la URI original sin cambios. 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 sigue siendo restrictivo.
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 vinculados a loopback y no a todas las interfaces.
sudo ss -tlnpCada línea de OpenTag debe 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 todo Internet y que sólo ufw lo está bloqueando. Un error de firewall bastaría para dejar abierto el activador del agente. Fundamentos del firewall ufw explica qué hace realmente esa denegación predeterminada.
Dos comprobaciones demuestran que la puerta de entrada funciona. curl -I https://opentag.example.com/ devuelve 404 desde nginx, lo que confirma que el certificado es válido y que el 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 solicitud escrita manualmente por alguien.
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 refuerzo del proyecto establecen la regla directamente: no acepte eventos de origen sin firma en /github/webhooks. Slack firma cada solicitud con SLACK_SIGNING_SECRET e incluye una marca de tiempo, por lo que un cuerpo capturado no se puede reutilizar horas después.
Omitir esta verificación supone un riesgo importante. Un endpoint sin verificación acepta una carga útil issue_comment escrita manualmente que contiene @opentag. Después, OpenTag ejecuta un agente de programación, con su token, en su checkout y siguiendo las 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 rastrean 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 un resultado correcto sin añadir otro evento de auditoría.
Los límites de velocidad son configurables y deben estar activados. OPENTAG_RATE_LIMIT_WINDOW_MS y OPENTAG_RATE_LIMIT_MAX_REQUESTS limitan la velocidad de las solicitudes, OPENTAG_MAX_REQUEST_BODY_BYTES limita el tamaño del cuerpo y una carga útil demasiado grande se rechaza con 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true existe para el desarrollo local y no debe usarse en un servidor público. Las mismas notas incluyen otra regla: 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 aplicación de GitHub. La documentación indica que la opción de aplicación está prevista, pero que actualmente 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. Cree el token en una cuenta cuyo nombre esté dispuesto a ver citado en cada respuesta de triaje.
Configure los permisos con el alcance mínimo indicado en la guía de instalación. Seleccione Only select repositories y elija uno. Conceda Issues: Read and write y Pull requests: Read and write. Eso 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 podrá dirigir un agente con permisos para hacer commits, y el registro de auditoría indicará que lo hizo el propietario del token. Amplíe el alcance 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, que comienza por xapp-. channels:history lee el historial de mensajes de los canales públicos a los que se haya añadido el bot. Por tanto, añada el bot a los canales donde deba usarse, no a todos.
Enrutar un problema de principio a fin
El webhook es el primer paso. En el repositorio, abra Settings, luego Webhooks y después 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 ninguna otra opción.
GitHub envía una entrega de ping en cuanto guarda la configuración. Abra Recent Deliveries y compruebe si la solicitud llegó al servidor. Un 502 indica que nginx no pudo llegar al proceso que escucha. Es un problema local, no de GitHub.
Ahora úselo. Abra un issue que describa un error y publique 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 del issue. sudo -iu opentag opentag status muestra la ejecución mientras está en curso, de modo que puede 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 una persona antes de realizar cualquier cambio de estado. Los modos auto y autonomous también existen y son opciones razonables más adelante, en un repositorio cuyo historial de transcripciones 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 cambiar las vinculaciones con OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, una lista de ID de usuario de Slack separados por comas, porque una vinculación es la relación entre un canal público y un checkout en su servidor.
Triage es una buena primera ruta porque lee datos y no escribe, y la respuesta es fácil de evaluar. Review es el siguiente nivel, en el que el agente comenta un diff en lugar de un issue: 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 servidores MCP en un VPS.
¿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 aparece como un comentario bajo un nombre que su 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. Diseñe el sistema para que la respuesta pueda ser incorrecta en público, no para que sea correcta en privado.
Cuatro decisiones limitan el daño y son más importantes que cualquier instrucción que escriba.
- Ejecute en modo
ask, de modo que el agente proponga, una persona apruebe y un plan incorrecto cueste un clic. - Mantenga
preparePullRequestBranchcon su valor predeterminado de false, de modo 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 ejecutor rechaza cualquier ejecución cuyo destino de proyecto esté fuera de su lista de permitidos local, por lo que un repositorio no vinculado no puede activar el agente.
- Mantenga separado el token para comentar y cualquier token para aplicar cambios, de modo que revocar el acceso de escritura no interrumpa la clasificación de incidencias.
Slack tiene un comando /stop para una ejecución que está tomando una dirección incorrecta. 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 permite determinar después 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 la presencia de una máquina y sepan que puede equivocarse. Una respuesta incorrecta expresada con confianza en un canal de cuarenta personas que creen que una persona la revisó cuesta más que el tiempo ahorrado en la clasificación. 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 consérvelas fuera del servidor. Si se pierden, tendrá que volver a crear los tokens y las vinculaciones, pero no reconstruir el 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 tanto, una versión publicada durante la noche introduce un cambio no revisado en ese entorno. La política de seguridad no incorpora correcciones en versiones anteriores. Las correcciones sólo llegan a la versión más reciente. Por eso, fijar la versión implica leer el registro de cambios y actualizar de forma deliberada. No significa permanecer para siempre en v0.9.0. El historial hasta julio de 2026 muestra varias versiones al mes. Es una buena razón para leer las notas de cada versión antes de actualizar.
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 es diferente. Los webhooks del repositorio se entregan mediante HTTP entrante a una URL que se registra una vez, por lo que la dirección debe mantenerse igual y responder mientras usted duerme. Un host de túnel de una cuenta gratuita cambia en cada reinicio, y GitHub sigue enviando solicitudes al 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 token de acceso personal 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 separado para mantener independiente el token que escribe código del token que publica comentarios. Evite un token para todos los repositorios con permiso contents write, porque cualquiera que pueda comentar en alguno de esos repositorios podría dirigir un agente capaz de hacer commits.
¿Cómo detengo una ejecución que está funcionando mal?
Slack tiene un comando /stop exactamente para esto. En el servidor, opentag status muestra lo que está en ejecución y opentag service stop detiene el daemon, lo que finaliza todo el flujo 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 esperen a 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, y significa que el proxy no pudo alcanzar el listener. /var/log/nginx/error.log mostrará connect() failed (111: Connection refused) while connecting to upstream. El listener está detenido o usa un puerto diferente del que indica la línea proxy_pass. Ejecute sudo ss -tlnp y confirme que algo está 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 executors.