Como hospedar o OpenTag para menções a agentes
Hospede o OpenTag v0.9.0 em um VPS para encaminhar menções do Slack e GitHub ao agente, com TLS, assinaturas de webhook e escopos de token seguros.
O que o OpenTag faz quando menciona um agente
O OpenTag transforma uma @menção numa thread do Slack ou numa issue do GitHub numa execução de um agente de programação numa máquina que você controla. Alguém comenta @opentag investigate this numa issue. Um listener recebe o evento da plataforma, verifica a sua assinatura, associa a menção a um projeto configurado, inicia um agente de programação sobre um checkout local e publica o resultado na mesma thread.
O projeto é licenciado sob a licença MIT e está disponível em amplifthq/opentag. Em agosto de 2026, a versão mais recente marcada é a v0.9.0, publicada em 28 July 2026, e é distribuída como um pacote npm. Não existe uma imagem de container oficial, portanto o elemento que deve ser fixado é a versão do npm. Todos os comandos abaixo fixam essa versão.
Isto transforma o projeto numa tarefa para um VPS, e não para um laptop, por causa da integração com o GitHub. O GitHub entrega eventos do repositório fazendo uma requisição HTTP para uma URL que você regista uma vez. Por isso, essa URL tem de responder no mesmo endereço amanhã.
As quatro partes móveis
O listener recebe eventos da plataforma, e cada plataforma tem o seu próprio listener. O listener do GitHub é um endpoint HTTP na porta 3050, no caminho /github/webhooks. O listener da Slack Events API está na porta 3040, em /slack/events. A Slack também pode funcionar no Socket Mode, em que a aplicação abre um WebSocket de saída e não precisa de nenhuma porta de entrada.
O dispatcher coordena o processo. Por predefinição, escuta na porta 3030, mantém o estado das execuções num ficheiro de base de dados local definido por OPENTAG_DATABASE_PATH e regista uma trilha de auditoria para cada execução. Nada fora do servidor deve conseguir aceder a esta porta.
O runner é o daemon local. Consulta se existem tarefas, assume uma execução, mantém um lease sobre ela e envia um heartbeat a cada 15 segundos por predefinição enquanto a execução estiver ativa. Recusa qualquer execução assumida cujo destino do projeto esteja ausente ou fora da allowlist na sua própria configuração. Esta verificação impede que um evento do GitHub aponte o seu agente para um repositório que nunca associou.
O executor é o próprio agente de programação. O OpenTag inicia-o através do ACP (agent client protocol), um protocolo JSON-RPC que usa a entrada e a saída padrão. Assim, o agente é executado como processo filho dentro de um diretório de trabalho que o OpenTag lhe fornece. Os nomes integrados incluem echo, codex, claude-code, cursor, opencode, hermes e openclaw. Comece com echo, o executor incluído na configuração de exemplo, porque isso confirma que todo o fluxo funciona antes de um modelo alterar o seu código.
A ordem nunca muda: evento da plataforma, verificação da assinatura, registo da execução, atribuição, agente, resposta na thread.
Por que um laptop e um túnel não são suficientes
O guia de configuração do GitHub manda executar ngrok http 3050 e colar o host do túnel no webhook do repositório. Isso funciona durante os primeiros dez minutos. O host de um túnel gratuito muda sempre que o processo é reiniciado e deixa de existir quando o laptop entra em suspensão. O GitHub mantém a URL de payload antiga e continua a tentar usá-la. Por isso, o separador Recent Deliveries nas definições do webhook enche-se de falhas, enquanto o thread permanece silencioso. Ninguém repara durante uma semana, porque um webhook que não faz nada parece exatamente um bot que ninguém mencionou.
Um VPS resolve os dois problemas. O nome DNS não muda, por isso a URL de payload que colar uma vez continua correta. A máquina não entra em suspensão, por isso um comentário às 02:00 recebe resposta. Configure primeiro o servidor corretamente: os primeiros dez minutos num VPS novo abrangem o utilizador de login e a firewall que este guia pressupõe.
O Slack é a exceção. No Socket Mode, liga-se ao exterior e não precisa de uma URL pública, por isso uma implementação apenas com Slack pode permanecer fechada. O GitHub não tem equivalente. Os webhooks de repositório são HTTP de entrada, o que exige um endpoint público, e isso exige TLS (transport layer security) e uma verificação de assinatura.
Self-host o OpenTag no Ubuntu a partir de uma versão fixada
O OpenTag v0.9.0 requer Node.js 22 ou posterior. O Ubuntu 24.04 disponibiliza o Node 18 no seu próprio repositório, por isso instale-o a partir do 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 deve imprimir v22 ou posterior. No Node 20, a instalação imprime um aviso EBADENGINE e a CLI pode falhar quando inicia.
Dê ao serviço uma conta própria. O agente é executado com as permissões desse utilizador, por isso não deve ser a sua conta de login nem root. Utilizadores com privilégios mínimos num VPS explica por que motivo vale a pena fazer essa separação.
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 deve imprimir um caminho como /usr/bin/opentag. A configuração de linger é importante no Linux: o OpenTag instala o serviço em segundo plano através do systemd, e um serviço de utilizador sem linger para assim que a sessão SSH é fechada.
Execute a configuração com esse utilizador.
sudo -iu opentag opentag setupA configuração pede seis dados: o idioma da CLI, o endereço de escuta local, o agente de programação, o projeto local onde deve trabalhar, as credenciais da plataforma a guardar e o modo de execução. Mantenha o endereço de escuta em 127.0.0.1, porque o nginx termina o TLS e encaminha o tráfego para esse endereço. Assim, os listeners nunca precisam de ser acessíveis a partir do exterior. Para o GitHub, também pede o repositório no formato owner/repo, se pode abrir pull requests, a porta do webhook (3050 por predefinição) e o token. No fim, escolha o modo de serviço em segundo plano. Se já tiver uma configuração e quiser instalar o serviço sem prompts, opentag setup --service faz isso.
A configuração fica em /home/opentag/.config/opentag/config.json e o estado de execução em /home/opentag/.local/state/opentag. Depois de a configuração escrever o ficheiro, confirme manualmente estas chaves.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}Prefira runnerToken, o bearer token específico do runner, ao antigo pairingToken partilhado. O ficheiro de configuração guarda as credenciais em texto simples, a menos que as substitua por uma referência a um segredo. Essa referência lê o valor do ambiente ou de um ficheiro no disco durante o arranque. Em qualquer dos casos, este ficheiro é o elemento mais sensível do servidor: deve ter o modo 600, pertencer a opentag e nunca ficar dentro de um repositório git. A explicação mais abrangente está em manter segredos fora dos agentes de IA.
Verifique a instalação antes de expor qualquer serviço.
sudo -iu opentag opentag doctor
sudo -iu opentag opentag statusopentag doctor verifica o dispatcher, os bindings, os checkouts e os executores. opentag status imprime a configuração e o estado de execução, e pode ser limitado a uma única execução quando já existirem execuções. Corrija tudo o que doctor indicar antes de apontar uma plataforma para este servidor.
Coloque TLS à frente e abra apenas dois caminhos
O nginx termina o TLS e encaminha exatamente dois caminhos. Tudo o resto devolve 404. Assim, um scanner que encontre o host não descobre o que está a executar por trás dele.
Escreva um bloco de servidor simples na porta 80 em /etc/nginx/sites-available/opentag, com as duas localizações abaixo. Depois, deixe o Certbot adicionar a 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 imprime syntax is ok e test is successful. É a única barreira entre um erro de digitação e um reload que deixa o site indisponível. Certbot no Ubuntu 24.04 com nginx explica a renovação e as formas como um desafio ACME (ambiente de gestão automática de certificados) pode falhar. O bloco final fica assim.
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;
}
}O = em location = /github/webhooks é uma correspondência exata, e proxy_pass sem nada depois da porta encaminha o URI original sem alterações. Remova = e todos os caminhos sob /github/webhooks/ também serão encaminhados. Isso expõe mais superfície do que o listener precisa.
A firewall continua restrita.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw statusAs portas 3030, 3040 e 3050 nunca são abertas. Confirme que estão associadas ao loopback, e não a todas as interfaces.
sudo ss -tlnpTodas as linhas OpenTag devem apresentar 127.0.0.1:3030 ou algo semelhante. Uma linha com 0.0.0.0:3050 significa que o listener está disponível para toda a internet e que apenas o ufw o está a bloquear. Basta um erro na firewall para ativar um agente exposto. Noções básicas da firewall ufw explica o que esse deny predefinido realmente faz.
Duas verificações comprovam a porta de entrada. curl -I https://opentag.example.com/ devolve 404 a partir do nginx. Isto confirma que o certificado é válido e que o catch-all está fechado. Um pedido para /slack/events ou /github/webhooks sem assinatura nunca deve devolver 200.
Verifique todas as assinaturas, porque o URL é público
Qualquer pessoa pode encontrar o URL do payload. Ele fica nas definições do repositório, no histórico do navegador ou numa captura de ecrã colada num ticket. A assinatura é o único elemento que distingue uma entrega real do GitHub de um pedido escrito manualmente por alguém.
O GitHub assina cada entrega com o segredo do webhook e envia o resultado no cabeçalho x-hub-signature-256. O OpenTag verifica esse cabeçalho com base em platforms.github.webhookSecret. As notas de hardening do projeto estabelecem diretamente a regra: não aceite eventos de origem sem assinatura em /github/webhooks. O Slack assina cada pedido com SLACK_SIGNING_SECRET e inclui um timestamp, para que um corpo capturado não possa ser reproduzido horas mais tarde.
Ignorar esta validação não representa um risco pequeno. Um endpoint sem verificação aceita um payload issue_comment escrito manualmente que contém @opentag. O OpenTag executa então um coding agent, com o seu token, no seu checkout e com instruções de um desconhecido. A resposta é enviada para o thread indicado pelo payload falso.
O OpenTag acrescenta duas camadas de proteção. As entregas de origem são controladas pelo ID de entrega, por isso reenviar o mesmo evento não inicia uma segunda execução. As chamadas do runner aceitam chaves de idempotência, por isso repetir uma chamada devolve sucesso sem acrescentar outro evento de auditoria.
Os limites de taxa são configuráveis e devem estar ativos. OPENTAG_RATE_LIMIT_WINDOW_MS e OPENTAG_RATE_LIMIT_MAX_REQUESTS limitam a taxa de pedidos, OPENTAG_MAX_REQUEST_BODY_BYTES limita o tamanho do corpo e um payload demasiado grande é rejeitado com 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true existe para o desenvolvimento local e não deve ser usado num servidor público. Há ainda outra regra nas mesmas notas: um URL de relay público deve usar HTTPS, e a CLI permite HTTP simples apenas para localhost.
De que escopos de token o bot realmente precisa?
No GitHub, o OpenTag usa um token de acesso pessoal de permissões granulares em vez de uma GitHub App. A documentação informa que o caminho da App está planeado e ainda não é a configuração padrão da CLI. Isso tem uma consequência que muitas pessoas ignoram: o bot comenta como a pessoa que criou o token. Crie-o numa conta cujo nome possa aparecer em todas as respostas de triagem.
Defina os escopos de forma tão restrita quanto no guia de configuração. Escolha Only select repositories e selecione um repositório. Conceda Issues: Read and write e Pull requests: Read and write. Isso é suficiente para ler uma menção e responder na thread.
Observe o que não está incluído: acesso de escrita ao código. O OpenTag não envia branches, a menos que preparePullRequestBranch esteja definido como true. Existe também um githubApplyToken separado para que o token que escreve código não seja o mesmo que escreve comentários. Mantenha-os separados e deixe o token de escrita desativado até que o fluxo de leitura e comentários tenha sido executado durante algumas semanas.
Evite configurar um token com Contents: Read and write em All repositories. Qualquer pessoa que possa comentar em algum desses repositórios poderá orientar um agente com permissões de commit, e o registo de auditoria indicará que o proprietário do token executou a ação. Amplie o escopo um repositório de cada vez, depois de o agente demonstrar que merece essa confiança.
No Slack, os escopos do bot são app_mentions:read, chat:write, reactions:write e channels:history. Os canais privados também precisam de groups:history e de uma subscrição ao evento message.groups. O Socket Mode precisa de um token ao nível da aplicação com connections:write, cujo valor começa por xapp-. channels:history lê o histórico de mensagens nos canais públicos aos quais o bot foi adicionado. Por isso, adicione o bot aos canais onde ele deve ser usado, em vez de o adicionar a todos.
Encaminhar um problema de ponta a ponta
O webhook vem primeiro. No repositório, abra Settings, depois Webhooks e, em seguida, Add webhook. O URL do payload é https://opentag.example.com/github/webhooks, o tipo de conteúdo é application/json e o segredo é o que foi gerado durante a configuração. Subscreva Issue comments e Pull request review comments, e mais nenhuma opção.
O GitHub envia uma entrega de ping assim que guardar. Abra Recent Deliveries e confirme se o pedido chegou sequer ao servidor. Um erro 502 nessa página significa que o nginx não conseguiu alcançar o listener. É um problema local, não do GitHub.
Agora utilize-o. Abra um issue que descreva um bug e publique este comentário:
@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.A sequência esperada é a seguinte. Recent Deliveries regista a entrega issue_comment com uma resposta 2xx. O dispatcher regista uma execução. O runner assume-a e começa a enviar heartbeats. O executor abre o checkout e executa o trabalho. A resposta chega como um comentário na mesma thread do issue. sudo -iu opentag opentag status mostra a execução enquanto está em curso, para que possa acompanhá-la em vez de tentar adivinhar o estado.
Defina approvalMode como ask antes da primeira execução real. No modo ask, a execução pausa e aguarda uma pessoa antes de fazer qualquer alteração de estado. Os modos auto e autonomous também existem e são opções razoáveis mais tarde, num repositório onde já tenha lido um mês de transcrições.
No lado do Slack, a mesma execução começa com /bind owner/repo no canal e, em seguida, com uma menção. O bot também responde a /help, /status, /doctor, /stop e /unbind confirm. Restrinja quem pode alterar os bindings com OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, uma lista de IDs de utilizadores do Slack separados por vírgulas, porque um binding é o mapeamento entre um canal público e um checkout no seu servidor.
Triage é uma boa primeira rota porque lê dados e não escreve, e a resposta é fácil de avaliar. Review é o passo seguinte, em que o agente comenta um diff em vez de um issue: um agente self-hosted de revisão de pull requests usa esta mesma arquitetura direcionada a pull requests. Se quiser que o agente aceda aos seus próprios sistemas enquanto trabalha, essa é a função de servidores MCP num VPS.
O que acontece quando o agente está errado à frente de toda a gente?
Ele vai estar errado. A questão é qual é o custo.
Uma resposta errada num issue público é um comentário associado a um nome que a sua equipa reconhece, e o GitHub envia-o por email a todas as pessoas subscritas assim que é publicado. Apagar o comentário não recolhe o email. O mesmo se aplica a uma notificação do Slack. Planeie para a resposta estar errada em público, e não para estar certa em privado.
Quatro escolhas limitam os danos e são mais importantes do que qualquer prompt que escreva.
- Execute no modo
ask, para que o agente proponha, uma pessoa aprove e um plano errado custe apenas um clique. - Mantenha
preparePullRequestBranchcom o valor predefinido false, para que o pior resultado de uma execução incorreta seja um comentário errado, e não uma branch errada. - Comece por associar um repositório e um canal. O runner rejeita qualquer execução cujo destino do projeto esteja fora da sua allowlist local. Assim, um repositório não associado não consegue iniciar o agente.
- Mantenha o token de comentários separado de qualquer token de aplicação, para que revogar o acesso de escrita não interrompa a triagem.
O Slack tem um comando /stop para uma execução que esteja a seguir uma direção errada. Cada execução também deixa um registo de auditoria com a menção que a iniciou e as ações do agente. É esse registo que deve consultar depois para perceber onde ocorreu o erro.
A componente social é tão importante como a configuração. Coloque o bot num canal onde as pessoas esperem uma máquina e saibam que ela pode estar errada. Uma resposta errada e confiante num canal com quarenta pessoas que presumem que um humano a reviu custa mais do que o tempo poupado na triagem. Indique na descrição do canal quem é responsável pelo bot e quem verifica os resultados.
Backups, upgrades e fixação de versão
Dois caminhos contêm tudo: /home/opentag/.config/opentag/config.json e /home/opentag/.local/state/opentag. O primeiro contém as suas credenciais; o segundo contém o histórico de execução e o ficheiro da base de dados. Faça cópias de segurança dos dois com o modo 600 e mantenha-as fora do servidor. Perdê-los significa recriar tokens e associações, não reconstruir um servidor.
As atualizações consistem em alterar a versão e reiniciar o serviço.
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 doctorFixe a versão em vez de acompanhar @latest. Este software executa um agente de programação no seu repositório com um token ativo. Por isso, uma versão publicada durante a noite representa uma alteração não revista a esse ambiente. A política de segurança não faz backports, e as correções só chegam à versão mais recente. Assim, fixar a versão significa ler o changelog e atualizar deliberadamente. Isto não significa permanecer para sempre na v0.9.0. O histórico até julho de 2026 mostra várias versões por mês. É uma boa razão para ler as notas de versão antes de cada atualização.
FAQ
Preciso de um VPS para executar o OpenTag ou um laptop é suficiente?
Um laptop é suficiente apenas para o Slack, porque o Socket Mode abre um WebSocket de saída e não precisa de uma porta de entrada. O GitHub é diferente. Os webhooks do repositório são entregues por HTTP de entrada num URL que regista uma vez. Por isso, o endereço tem de permanecer igual e responder enquanto dorme. O endereço de um host de túnel de uma conta gratuita muda a cada reinício. O GitHub continua a enviar pedidos para o endereço antigo. Isto aparece como entradas com falha no separador Recent Deliveries do repositório e como silêncio na thread. Um VPS com um nome DNS fixo e um certificado elimina os dois problemas.
De que permissões do GitHub precisa o OpenTag?
Um personal access token com permissões granulares, limitado a Only select repositories, com Issues: Read and write e Pull requests: Read and write. Isto permite ler uma menção e responder na thread. Não é necessário acesso de escrita ao código, exceto se definir preparePullRequestBranch como true para que o OpenTag faça push de branches. Existe um githubApplyToken separado para manter o token que escreve código separado do token que publica comentários. Evite um token para todos os repositórios com contents write. Qualquer pessoa que possa comentar em qualquer um desses repositórios poderia então orientar um agente com capacidade para fazer commits.
Como paro uma execução que está a correr mal?
O Slack tem um comando /stop específico para isso. No servidor, opentag status mostra o que está em execução e opentag service stop para o daemon. Isto termina todo o pipeline, não apenas uma execução. Para não precisar de nenhum dos dois, defina approvalMode como ask. Assim, as execuções ficam pausadas para revisão humana antes de alterarem qualquer coisa. Mantenha preparePullRequestBranch como false para que uma execução com problemas produza um comentário em vez de uma branch.
Porque é que o meu webhook devolve 502 enquanto a thread permanece silenciosa?
O 502 vem do nginx, não do OpenTag. Significa que o proxy não conseguiu alcançar o listener. /var/log/nginx/error.log mostra connect() failed (111: Connection refused) while connecting to upstream. O listener está parado ou usa uma porta diferente da indicada na linha proxy_pass. Execute sudo ss -tlnp e confirme que existe algo a escutar em 127.0.0.1:3050 para o GitHub e em 127.0.0.1:3040 para o Slack. Depois, execute opentag doctor para verificar os bindings e os executors.