Como hospedar o OpenTag para menções @agent
Hospede o OpenTag v0.9.0 em um VPS e encaminhe menções do Slack e GitHub ao agente, com TLS, assinaturas de webhook, escopos e padrões seguros.
O que o OpenTag faz quando você menciona um agente
O OpenTag transforma uma menção com @ em uma thread do Slack ou em uma issue do GitHub numa execução de 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 assinatura, associa a menção a um projeto vinculado, inicia um agente de programação num checkout local e publica o resultado de volta 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 de julho de 2026, e é distribuída como um pacote npm. Não existe uma imagem de container oficial, portanto o que você fixa é 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 um URL que você registra uma vez. Portanto, esse URL precisa 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 é o coordenador. 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 uma 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 em falta ou fora da allowlist na sua própria configuração. Esta é a verificação que impede um evento do GitHub de apontar o 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 utiliza a entrada e a saída padrão. Assim, o agente é executado como um processo filho dentro do diretório de trabalho que o OpenTag lhe fornece. Os nomes integrados incluem echo, codex, claude-code, cursor, opencode, hermes e openclaw. Comece por echo, o executor fornecido pela configuração de exemplo, porque isso confirma que todo o fluxo funciona antes de um modelo alterar o seu código. Se o ciclo que lê um prompt, chama ferramentas e decide quando terminou ainda for uma caixa-preta, escrever primeiro um pequeno ciclo de agente torna os modos de falha deste pipeline muito mais fáceis de identificar.
A ordem nunca muda: evento da plataforma, verificação da assinatura, registo da execução, atribuição, agente, resposta na thread.
Por que um portátil e um túnel não são suficientes
O guia de configuração do GitHub indica que deve executar ngrok http 3050 e colar o host do túnel no webhook do repositório. Isso funciona durante os primeiros dez minutos. Um host de túnel gratuito muda sempre que o processo é reiniciado e deixa de existir quando o portátil entra em suspensão. O GitHub mantém o URL de payload antigo e continua a tentar utilizá-lo, por isso o separador Recent Deliveries nas definições do webhook fica cheio de falhas enquanto a thread permanece silenciosa. Ninguém repara durante uma semana, porque um webhook que não faz nada tem exatamente o mesmo aspeto que um bot que ninguém mencionou.
Um VPS resolve os dois problemas que causam estas falhas. O nome DNS não muda, por isso o URL de payload que cola uma vez continua correto. A máquina não entra em suspensão, por isso um comentário às 02:00 recebe uma resposta. Configure primeiro o sistema corretamente: os primeiros dez minutos num VPS novo abrangem o utilizador de login e a firewall assumidos por este guia.
O Slack é a exceção. No Socket Mode, estabelece ligações de saída e não precisa de um URL público, por isso uma implementação apenas para Slack pode permanecer fechada. O GitHub não tem um equivalente. Os webhooks de repositórios são HTTP de entrada, o que implica um endpoint público e, portanto, TLS (transport layer security) e uma verificação de assinatura.
Self-host o OpenTag no Ubuntu a partir de uma release 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 apresentar v22 ou superior. No Node 20, a instalação apresenta um aviso EBADENGINE e a CLI pode falhar quando arranca.
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 numa VPS explica por que motivo vale a pena fazer esta 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 apresentar um caminho como /usr/bin/opentag. A configuração de linger é importante no Linux: o OpenTag instala o seu 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 faz seis perguntas: o idioma da CLI, o endereço de escuta local, o agente de programação, o projeto local em que 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 as ligações para esse endereço. Assim, os listeners nunca precisam de estar acessíveis a partir do exterior. Para o GitHub, também são pedidos o repositório no formato owner/repo, a autorização para 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 gravar o ficheiro, vale a pena verificar estas chaves manualmente.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}Prefira runnerToken, o bearer token com âmbito 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 a partir do ambiente ou de um ficheiro no disco durante o arranque. Em qualquer dos casos, este ficheiro é o conteúdo mais sensível do servidor: deve ter o modo 600, pertencer a opentag e nunca ficar dentro de um repositório git. O argumento 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 executors. opentag status apresenta 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. Todos os restantes devolvem 404. Assim, um scanner que encontre o host não fica a saber o que está a ser executado atrá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 situações em que falha um desafio ACME (ambiente de gestão automática de certificados). O bloco concluído 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. proxy_pass sem nada depois da porta transmite o URI original sem alterações. Remova = e todos os caminhos sob /github/webhooks/ também serão encaminhados. Isso expõe mais do que o listener precisa.
O firewall permanece restrito.
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 -tlnpCada linha OpenTag deve 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 no firewall para ativar um agente exposto. conceitos básicos do firewall ufw explica o que essa negação predefinida realmente faz.
Duas verificações confirmam a porta de entrada. curl -I https://opentag.example.com/ devolve 404 do nginx, o que mostra 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 cada assinatura, porque o URL é público
Qualquer pessoa pode encontrar o URL do payload. Ele aparece 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 introduzido 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 usando 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 depois.
Ignorar esta verificaçã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, usando o seu token, no seu checkout e com instruções de um desconhecido. A resposta é enviada para a thread indicada pelo payload falso.
O OpenTag acrescenta duas camadas. As entregas de origem são controladas pelo ID da 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 ativados. OPENTAG_RATE_LIMIT_WINDOW_MS e OPENTAG_RATE_LIMIT_MAX_REQUESTS limitam a taxa de pedidos, OPENTAG_MAX_REQUEST_BODY_BYTES limita o corpo do pedido 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. Outra regra das mesmas notas: um URL de relay público tem de usar HTTPS, e a CLI permite HTTP simples apenas para localhost.
De que escopos de token o bot precisa realmente?
No GitHub, o OpenTag usa um token de acesso pessoal com permissões granulares, em vez de uma GitHub App. A documentação indica que o caminho da App está planeado e que atualmente não é a configuração CLI predefinida. Isto tem uma consequência que muitas pessoas ignoram: o bot publica comentários em nome da pessoa que criou o token. Crie-o numa conta cujo nome esteja disposto a ver citado em todas as respostas de triagem.
Defina os escopos de forma tão restrita quanto indicado 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 discussão.
Observe o que não está incluído: acesso de escrita ao código. O OpenTag não envia branches, exceto quando preparePullRequestBranch está definido como true. Também existe um githubApplyToken separado, para que o token que escreve código não seja o mesmo que publica comentários. Mantenha-os separados e não ative o token de escrita até o fluxo de leitura e comentários ter sido executado durante algumas semanas.
A configuração a evitar é um token com Contents: Read and write em All repositories. Qualquer pessoa que possa comentar em qualquer um desses repositórios passa a poder orientar um agente com permissões de commit, e o registo de auditoria indica que o proprietário do token executou a ação. Alargue o escopo um repositório de cada vez, depois de o agente o ter justificado.
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 do evento message.groups. O Socket Mode precisa de um token ao nível da aplicação com connections:write, que 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 é necessário, 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 pela configuração. Subscreva Issue comments e Pull request review comments, e nenhuma outra opção.
O GitHub envia uma entrega de ping assim que guardar a configuração. Abra Recent Deliveries e confirme se o pedido chegou ao servidor. Um erro 502 nesse ponto significa que o nginx não conseguiu alcançar o processo que está a escutar, o que indica um problema local, não um problema 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 comentário no mesmo tópico 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 em que já tenha analisado um mês de transcrições.
No Slack, a mesma execução começa com /bind owner/repo no canal e, depois, 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.
A triagem é uma boa primeira opção porque lê dados e não faz alterações, e a resposta é fácil de avaliar. A revisão é 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 para pull requests. Se quiser que o agente aceda aos seus próprios sistemas durante o trabalho, essa é a função de servidores MCP numa VPS. A pesquisa na Web é a outra capacidade que a triagem solicita frequentemente, e ligar o agente à sua própria instância SearXNG mantém essas pesquisas no hardware que administra, ao custo de acrescentar um canal através do qual texto de terceiros chega ao agente.
O que acontece quando o agente erra perante toda a gente?
Vai errar. A questão é saber 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 permite recuperar o email. O mesmo se aplica a uma notificação do Slack. Planeie para a possibilidade de a resposta estar errada em público, em vez de contar com a possibilidade de 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. - Associe inicialmente um repositório e um canal. O runner rejeita qualquer execução cujo destino do projeto esteja fora da sua allowlist local, pelo que um repositório não associado não consegue iniciar o agente por si próprio.
- Mantenha o token de comentários separado de qualquer token de aplicação, para que a revogação do acesso de escrita não interrompa a triagem.
O Slack tem um comando /stop para uma execução que esteja a seguir na 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 dimensão social é tão importante como a configuração. Coloque o bot num canal onde as pessoas esperem a presença de uma máquina e saibam que ela pode errar. 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. Escreva na descrição do canal quem é responsável pelo bot e quem verifica os resultados.
Backups, atualizações 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 de ambos 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 nesse acesso. A política de segurança não faz backports, e as correções só são disponibilizadas na versão mais recente. Fixar a versão significa ler o changelog e atualizar deliberadamente. Isso 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. Por isso, é recomendável ler as notas de versão antes de cada atualização.
FAQ
Preciso de um VPS para executar o OpenTag ou um portátil é suficiente?
Um portátil é 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 tem de responder enquanto dorme. Um host de túnel de uma conta gratuita muda a cada reinício, e o GitHub continua a enviar pedidos para o endereço antigo. Isto aparece como entradas falhadas no separador Recent Deliveries do repositório e como silêncio na thread. Um VPS com um nome DNS fixo e um certificado elimina ambos os problemas.
De que permissões do GitHub precisa o OpenTag?
Um token de acesso pessoal de 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 envie branches. Existe também 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 permissão de escrita em contents, porque qualquer pessoa que possa comentar em qualquer um desses repositórios poderia controlar um agente capaz de fazer commits.
Como paro uma execução que está a correr mal?
O Slack tem um comando /stop específico para isto. No servidor, opentag status mostra o que está em execução, e opentag service stop para o daemon, terminando todo o pipeline em vez de apenas uma execução. Para evitar precisar de qualquer um deles, defina approvalMode como ask, para que as execuções aguardem a confirmação de uma pessoa antes de alterarem qualquer coisa, e mantenha preparePullRequestBranch como false, para que uma execução incorreta 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, e 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 está numa porta diferente da indicada pela 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. Em seguida, execute opentag doctor para verificar os bindings e os executors.