Come eseguire OpenTag self-hosted per le menzioni @agent
Configura OpenTag v0.9.0 su un VPS: TLS, firme webhook Slack e GitHub, scope dei token e impostazioni sicure per inoltrare le menzioni al coding agent.
Cosa fa OpenTag quando si menziona un agent
OpenTag trasforma una menzione @ in un thread Slack o in una issue GitHub in un'esecuzione di un coding agent su una macchina di tua proprietà. Qualcuno commenta @opentag investigate this in una issue. Un listener riceve l'evento della piattaforma, ne verifica la firma, associa la menzione a un progetto configurato, avvia un coding agent su una copia di lavoro locale e pubblica il risultato nello stesso thread.
Il progetto è distribuito con licenza MIT e si trova in amplifthq/opentag. Ad agosto 2026, la release con il tag più recente è v0.9.0, pubblicata il 28 luglio 2026, ed è distribuita come pacchetto npm. Non esiste un'immagine container ufficiale, quindi il riferimento da fissare è la versione npm. Ogni comando riportato di seguito la fissa.
Questo diventa un progetto per VPS anziché per un laptop a causa dell'integrazione con GitHub. GitHub invia gli eventi del repository effettuando una richiesta HTTP a un URL registrato una sola volta, quindi quell'URL deve rispondere allo stesso indirizzo anche il giorno successivo.
I quattro componenti
Il listener riceve gli eventi della piattaforma e ogni piattaforma ne usa uno specifico. Il listener GitHub è un endpoint HTTP sulla porta 3050, al percorso /github/webhooks. Il listener della Slack Events API è sulla porta 3040, all’indirizzo /slack/events. Slack può anche usare Socket Mode: in questo caso l’applicazione apre un WebSocket in uscita e non richiede alcuna porta in ingresso.
Il dispatcher coordina le operazioni. È in ascolto sulla porta 3030 per impostazione predefinita, conserva lo stato delle esecuzioni in un file di database locale impostato da OPENTAG_DATABASE_PATH e registra una traccia di audit per ogni esecuzione. Nessun sistema esterno deve mai poter raggiungere questa porta.
Il runner è il demone locale. Esegue il polling del lavoro disponibile, acquisisce un’esecuzione, mantiene il relativo lease e invia un heartbeat ogni 15 secondi per impostazione predefinita mentre l’esecuzione è attiva. Rifiuta ogni esecuzione acquisita se la destinazione del progetto non esiste o si trova al di fuori dell’allowlist definita nella propria configurazione. Questo controllo impedisce a un evento GitHub di indirizzare l’agent verso un repository a cui non è mai stato associato.
L'executor è l'agente che esegue il codice. OpenTag lo avvia tramite ACP (Agent Client Protocol), un protocollo JSON-RPC che usa l'input e l'output standard. L'agente viene quindi eseguito come processo figlio nella directory di lavoro che OpenTag gli assegna. I nomi integrati includono echo, codex, claude-code, cursor, opencode, hermes e openclaw. Inizia con echo, l'executor incluso nella configurazione di esempio, perché consente di verificare l'intero flusso prima che un modello modifichi il codice. Se il ciclo che legge un prompt, richiama gli strumenti e decide quando ha terminato ti è ancora poco chiaro, scrivere prima un piccolo ciclo dell'agente rende molto più facili da interpretare le modalità di errore di questa pipeline.
L’ordine non cambia mai: evento della piattaforma, verifica della firma, registrazione dell’esecuzione, acquisizione, agent, risposta nel thread.
Perché un laptop e un tunnel non bastano
La guida di configurazione di GitHub indica di eseguire ngrok http 3050 e incollare l'host del tunnel nel webhook del repository. Funziona per i primi dieci minuti. L'host di un tunnel gratuito cambia ogni volta che il processo viene riavviato e smette di esistere quando il laptop entra in sospensione. GitHub conserva il vecchio payload URL e continua a usarlo, quindi la scheda Recent Deliveries nelle impostazioni del webhook si riempie di errori mentre il thread resta inattivo. Nessuno se ne accorge per una settimana, perché un webhook che non fa nulla sembra esattamente un bot di cui nessuno ha parlato.
Un VPS risolve i due problemi che causano il malfunzionamento. Il nome DNS non cambia, quindi il payload URL inserito una volta resta corretto. La macchina non entra in sospensione, quindi un commento pubblicato alle 02:00 riceve una risposta. Configura correttamente il server prima di procedere: i primi dieci minuti su un nuovo VPS descrivono l'utente per il login e il firewall presupposti da questa guida.
Slack è l'eccezione. In Socket Mode stabilisce connessioni in uscita e non richiede un URL pubblico, quindi una distribuzione che usa solo Slack può restare chiusa. GitHub non offre un equivalente. I webhook dei repository sono richieste HTTP in ingresso, quindi richiedono un endpoint pubblico e, di conseguenza, TLS (transport layer security) e un controllo della firma.
Self-host OpenTag su Ubuntu da una release fissata
OpenTag v0.9.0 richiede Node.js 22 o una versione successiva. Ubuntu 24.04 include Node 18 nel proprio repository, quindi installalo da 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 restituire v22 o una versione successiva. Con Node 20 l'installazione visualizza un avviso EBADENGINE e la CLI può non funzionare all'avvio.
Assegna al servizio un account dedicato. L'agent viene eseguito con i permessi di questo utente, quindi l'account non deve essere quello del tuo accesso e non deve essere root. Utenti con privilegi minimi su un VPS spiega perché questa separazione giustifica il passaggio aggiuntivo.
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 dovrebbe restituire un percorso come /usr/bin/opentag. L'impostazione linger è importante su Linux: OpenTag installa il servizio in background tramite systemd e un servizio utente senza linger si arresta non appena la sessione SSH viene chiusa.
Esegui la configurazione con questo utente.
sudo -iu opentag opentag setupLa configurazione richiede sei informazioni: la lingua della CLI, l'indirizzo di ascolto locale, l'agent di coding, il progetto locale su cui lavorare, le credenziali della piattaforma da salvare e la modalità di esecuzione. Mantieni l'indirizzo di ascolto su 127.0.0.1, perché nginx termina TLS e inoltra le richieste a quell'indirizzo. In questo modo i listener non devono essere raggiungibili dall'esterno. Per GitHub viene inoltre richiesto il repository nel formato owner/repo, se può aprire pull request, la porta del webhook (3050 per impostazione predefinita) e il token. Alla fine scegli la modalità di servizio in background. Se hai già una configurazione e vuoi installare il servizio senza richieste interattive, opentag setup --service esegue questa operazione.
La configurazione viene salvata in /home/opentag/.config/opentag/config.json e lo stato di runtime in /home/opentag/.local/state/opentag. Dopo la scrittura del file, conviene verificare manualmente queste chiavi.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}Preferisci runnerToken, il bearer token con ambito limitato al runner, al precedente pairingToken condiviso. Il file di configurazione contiene le credenziali in testo in chiaro, a meno che tu non le sostituisca con un riferimento a un secret. Il riferimento legge il valore dall'ambiente o da un file sul disco all'avvio. In entrambi i casi, questo file è l'elemento più sensibile del server: deve avere la modalità 600, appartenere a opentag e non deve mai trovarsi in un repository git. L'argomentazione generale è disponibile in mantenere i secret fuori dagli agent AI.
Verifica l'installazione prima di esporre il servizio.
sudo -iu opentag opentag doctor
sudo -iu opentag opentag statusopentag doctor controlla il dispatcher, i binding, i checkout e gli executor. opentag status stampa la configurazione e lo stato di runtime e può essere limitato a una singola esecuzione quando sono presenti delle esecuzioni. Correggi tutto ciò che doctor segnala prima di collegare una piattaforma a questo server.
Metti TLS davanti ai servizi ed esponi soltanto due percorsi
nginx termina TLS e inoltra esattamente due percorsi. Tutto il resto restituisce 404, quindi uno scanner che rileva l'host non può sapere che cosa è in esecuzione dietro il proxy.
Scrivi un blocco server sulla porta 80 in /etc/nginx/sites-available/opentag con le due location riportate di seguito, quindi lascia che Certbot aggiunga 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 visualizza syntax is ok e test is successful. È l'unico elemento tra un errore di battitura e un reload che rende il sito irraggiungibile. Certbot su Ubuntu 24.04 con nginx descrive il rinnovo e i casi in cui una challenge ACME (automatic certificate management environment) non riesce. Il blocco completo è il seguente.
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;
}
}Il = in location = /github/webhooks è una corrispondenza esatta, mentre proxy_pass senza nulla dopo la porta inoltra l'URI originale senza modificarlo. Se rimuovi =, vengono inoltrati anche tutti i percorsi sotto /github/webhooks/, esponendo una superficie maggiore di quella necessaria al listener.
Il firewall resta restrittivo.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw statusLe porte 3030, 3040 e 3050 non vengono mai aperte. Verifica che siano associate all'interfaccia loopback e non a tutte le interfacce.
sudo ss -tlnpOgni riga OpenTag dovrebbe contenere 127.0.0.1:3030 o un valore simile. Una riga con 0.0.0.0:3050 indica che il listener è esposto a tutta Internet e che solo ufw ne impedisce l'accesso. Un singolo errore nella configurazione del firewall può quindi attivare un endpoint dell'agente. Nozioni di base sul firewall ufw spiega che cosa impedisce realmente quel deny predefinito.
Due controlli dimostrano che il punto di accesso è configurato correttamente. curl -I https://opentag.example.com/ restituisce 404 da nginx, a conferma che il certificato è valido e che il catch-all è chiuso. Una richiesta a /slack/events o /github/webhooks senza firma non deve mai restituire 200.
Verifica ogni firma, perché l'URL è pubblico
Chiunque può trovare l'URL del payload. Può trovarlo nelle impostazioni del repository, nella cronologia del browser o in uno screenshot incollato in un ticket. La firma è l'unico elemento che distingue una delivery reale di GitHub da una richiesta inserita manualmente.
GitHub firma ogni delivery usando il webhook secret e invia il risultato nell'header x-hub-signature-256. OpenTag verifica quell'header rispetto a platforms.github.webhookSecret. Le note di hardening del progetto stabiliscono direttamente la regola: non accettare source event senza firma su /github/webhooks. Slack firma ogni richiesta con SLACK_SIGNING_SECRET e include un timestamp, quindi un body intercettato non può essere riprodotto ore dopo.
Ignorare questo controllo comporta un rischio concreto. Un endpoint non verificato accetta un payload issue_comment scritto manualmente che contiene @opentag. OpenTag avvia quindi un coding agent, usando il tuo token, nella tua checkout e seguendo istruzioni di uno sconosciuto. La risposta viene inviata al thread indicato dal payload contraffatto.
OpenTag aggiunge due livelli di protezione. Le source delivery vengono tracciate tramite il delivery ID, quindi una nuova consegna dello stesso evento non avvia una seconda esecuzione. Le chiamate del runner accettano idempotency key, quindi la riproduzione di una chiamata restituisce un esito positivo senza aggiungere un altro audit event.
I rate limit sono configurabili e devono essere attivati. OPENTAG_RATE_LIMIT_WINDOW_MS e OPENTAG_RATE_LIMIT_MAX_REQUESTS limitano il rate delle richieste, OPENTAG_MAX_REQUEST_BODY_BYTES limita la dimensione del body e un payload troppo grande viene rifiutato con 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true è previsto per lo sviluppo locale e non deve essere usato su un server pubblico. Le stesse note stabiliscono un'ulteriore regola: un URL di relay pubblico deve usare HTTPS e la CLI consente HTTP in chiaro soltanto per localhost.
Quali ambiti dei token servono effettivamente al bot?
Su GitHub, OpenTag usa un token di accesso personale a granularità fine anziché una GitHub App. La documentazione indica che l'integrazione tramite App è prevista, ma oggi non è il percorso predefinito per la configurazione CLI. Questo comporta una conseguenza spesso trascurata: il bot pubblica i commenti come l'utente umano che ha creato il token. Crealo con un account il cui nome possa comparire in ogni risposta di triage.
Configura gli ambiti in modo restrittivo, come indicato nella guida di configurazione. Scegli Only select repositories e seleziona un solo repository. Concedi Issues: Read and write e Pull requests: Read and write. È sufficiente per leggere una menzione e rispondere nella discussione.
Nota cosa manca: l'accesso in scrittura al codice. OpenTag non esegue il push di branch, a meno che preparePullRequestBranch non sia impostato su true. Esiste inoltre un githubApplyToken separato, in modo che il token che scrive il codice non sia lo stesso che pubblica i commenti. Tienili separati e lascia disabilitato il token di scrittura finché il percorso di sola lettura e commento non sarà stato utilizzato per alcune settimane.
La configurazione da evitare prevede un token con Contents: Read and write su All repositories. Chiunque possa pubblicare commenti in uno di questi repository può quindi indirizzare un agente che dispone dei permessi di commit, mentre il registro di audit attribuisce l'azione al proprietario del token. Estendi l'ambito a un repository alla volta, dopo che l'agente avrà dimostrato di meritare questo livello di accesso.
Su Slack gli ambiti del bot sono app_mentions:read, chat:write, reactions:write e channels:history. I canali privati richiedono anche groups:history e una sottoscrizione all'evento message.groups. Socket Mode richiede un token a livello di app con connections:write, quello che inizia con xapp-. channels:history legge la cronologia dei messaggi nei canali pubblici ai quali è stato aggiunto il bot. Aggiungi quindi il bot ai soli canali in cui deve essere utilizzato, non a tutti.
Gestire un problema dall'inizio alla fine
Il webhook viene prima. Nel repository, apri Settings, quindi Webhooks e infine Add webhook. L'URL del payload è https://opentag.example.com/github/webhooks, il tipo di contenuto è application/json e il secret è quello generato dalla procedura di configurazione. Sottoscrivi Issue comments e Pull request review comments, senza selezionare altro.
GitHub invia una delivery di ping subito dopo il salvataggio. Apri Recent Deliveries e verifica che la richiesta abbia raggiunto almeno il server. Un codice 502 indica che nginx non ha potuto raggiungere il listener. È un problema locale, non di GitHub.
Ora prova il flusso. Apri un issue che descrive un bug e pubblica questo commento:
@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.Ecco cosa deve accadere, nell'ordine. Recent Deliveries registra la delivery issue_comment con una risposta 2xx. Il dispatcher registra un'esecuzione. Il runner la prende in carico e inizia a inviare heartbeat. L'executor apre il checkout ed esegue il lavoro. La risposta arriva come commento nello stesso thread dell'issue. sudo -iu opentag opentag status mostra l'esecuzione mentre è in corso, quindi puoi monitorarla senza doverla dedurre dai sintomi.
Imposta approvalMode su ask prima della prima esecuzione reale. In modalità ask l'esecuzione si interrompe e attende una persona prima di eseguire qualsiasi operazione che modifichi lo stato. Esistono anche le modalità auto e autonomous. Sono opzioni ragionevoli in seguito, su un repository per il quale hai già esaminato un mese di transcript.
Sul lato Slack, la stessa esecuzione inizia con /bind owner/repo nel canale, seguita da una mention. Il bot risponde anche a /help, /status, /doctor, /stop e /unbind confirm. Limita gli utenti autorizzati a modificare i binding con OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, un elenco di Slack user ID separati da virgole, perché un binding associa un canale pubblico a un checkout sul tuo server.
Il triage è un buon primo flusso perché legge i dati senza modificarli e la risposta è facile da verificare. Il passaggio successivo è la review, in cui l'agent commenta un diff invece di un issue: un agent self-hosted per la review delle pull request usa questa stessa architettura con le pull request. Se vuoi che l'agent raggiunga i tuoi sistemi durante l'esecuzione, questo è il compito dei server MCP su un VPS. La ricerca web è l'altra funzionalità richiesta spesso dal triage e collegare l'agent alla tua istanza SearXNG mantiene queste ricerche sull'hardware che gestisci tu, al prezzo di aggiungere un altro canale attraverso il quale il testo di utenti esterni raggiunge l'agent.
Cosa succede quando l’agente sbaglia davanti a tutti?
Sbaglierà. La domanda è quanto costa.
Una risposta errata in un issue pubblico è un commento pubblicato con un nome che il team riconosce, e GitHub lo invia tramite email a tutti gli iscritti non appena viene pubblicato. Eliminare il commento non ritira l’email. Lo stesso vale per una notifica di Slack. Occorre prepararsi alla possibilità che la risposta sia errata pubblicamente, non presumere che sia corretta privatamente.
Quattro scelte limitano i danni e sono più importanti di qualsiasi prompt:
- Eseguire in modalità
ask, in modo che l’agente proponga, una persona approvi e un piano errato costi un solo clic. - Lasciare
preparePullRequestBranchal valore predefinito false, in modo che l’esito peggiore di un’esecuzione errata sia un commento sbagliato, non un branch modificato. - Associare inizialmente un solo repository e un solo canale. Il runner rifiuta qualsiasi esecuzione il cui target di progetto non rientri nella relativa allowlist locale, quindi un repository non associato non può attivare l’agente.
- Tenere separato il token per i commenti da qualsiasi token per l’applicazione delle modifiche, in modo che la revoca dell’accesso in scrittura non interrompa il triage.
Slack dispone del comando /stop per un’esecuzione che sta procedendo nella direzione sbagliata. Ogni esecuzione lascia inoltre un record di audit contenente la menzione che l’ha avviata e le operazioni eseguite dall’agente. Questo record consente di ricostruire successivamente dove si è verificato l’errore.
Anche l’aspetto sociale è importante quanto la configurazione. Inserire il bot in un solo canale, dove le persone si aspettano la presenza di un sistema automatico e sanno che può sbagliare. Una risposta errata e formulata con sicurezza in un canale con quaranta persone, che presumono sia stata verificata da un essere umano, può costare più del tempo risparmiato dal triage. Indicare nella descrizione del canale chi è responsabile del bot e chi verifica il suo output.
Backup, aggiornamenti e versionamento fisso
Due percorsi contengono tutto: /home/opentag/.config/opentag/config.json e /home/opentag/.local/state/opentag. Il primo contiene le credenziali, il secondo conserva la cronologia delle esecuzioni e il file del database. Esegui il backup di entrambi con modalità 600 e conservali fuori dal server. Se li perdi, devi ricreare token e associazioni, non ricostruire un server.
Gli aggiornamenti consistono nell'incrementare la versione e riavviare il servizio.
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 doctorBlocca la versione invece di seguire @latest. Questo software esegue un coding agent sul tuo repository usando un token attivo, quindi una release pubblicata durante la notte introduce una modifica non revisionata a quel repository. La policy di sicurezza non applica backport e le correzioni arrivano soltanto nella release più recente. Per questo il versionamento fisso richiede di leggere il changelog e aggiornare intenzionalmente. Non significa restare per sempre su v0.9.0. La cronologia fino a luglio 2026 mostra diverse release al mese. È quindi opportuno leggere le release note prima di ogni aggiornamento di versione.
FAQ
Mi serve un VPS per eseguire OpenTag o è sufficiente un laptop?
Un laptop è sufficiente per usare solo Slack, perché Socket Mode apre un WebSocket in uscita e non richiede porte in ingresso. GitHub funziona in modo diverso. I webhook dei repository vengono inviati tramite HTTP in ingresso a un URL registrato una volta sola. L'indirizzo deve quindi rimanere invariato e rispondere anche quando non si è al computer. Un host tunnel ottenuto con un account gratuito cambia a ogni riavvio e GitHub continua a inviare le richieste al vecchio indirizzo. Nel repository questo risulta come consegne non riuscite nella scheda Recent Deliveries e come assenza di messaggi nel thread. Un VPS con un nome DNS fisso e un certificato risolve entrambi i problemi.
Quali autorizzazioni GitHub sono necessarie per OpenTag?
Un personal access token con autorizzazioni granulari, limitato a Only select repositories, con Issues: Read and write e Pull requests: Read and write. Queste autorizzazioni consentono di leggere una menzione e rispondere nel thread. L'accesso in scrittura al codice non è necessario, a meno che preparePullRequestBranch non sia impostato su true per consentire a OpenTag di fare il push dei branch. È disponibile anche githubApplyToken, che permette di mantenere separato il token con accesso alla scrittura del codice da quello usato per i commenti. Evitare un token valido per tutti i repository con autorizzazione contents write. Chiunque possa commentare in uno di quei repository potrebbe infatti indirizzare un agent che ha il permesso di eseguire commit.
Come posso interrompere un'esecuzione che sta procedendo in modo errato?
Slack dispone del comando /stop proprio per questo scopo. Sul server, opentag status mostra ciò che è in esecuzione e opentag service stop arresta il daemon, terminando l'intera pipeline invece di una sola esecuzione. Per non dover usare nessuno dei due comandi, impostare approvalMode su ask, in modo che le esecuzioni vengano sospese in attesa dell'intervento di una persona prima di apportare modifiche, e lasciare preparePullRequestBranch su false, così un'esecuzione errata produce un commento invece di un branch.
Perché il webhook restituisce 502 mentre il thread rimane senza messaggi?
Il codice 502 proviene da nginx, non da OpenTag, e indica che il proxy non ha potuto raggiungere il listener. /var/log/nginx/error.log mostrerà connect() failed (111: Connection refused) while connecting to upstream. Il listener è arrestato oppure usa una porta diversa da quella indicata dalla riga proxy_pass. Eseguire sudo ss -tlnp e verificare che qualcosa sia in ascolto su 127.0.0.1:3050 per GitHub e su 127.0.0.1:3040 per Slack. Quindi eseguire opentag doctor per controllare binding ed executor.