OpenTag zelf hosten voor coding agent mentions
Host OpenTag op uw eigen VPS om Slack en GitHub mentions te koppelen aan uw coding agent. Leer hoe u TLS ingress, webhook verificatie en token scopes correct configureert.
Wat OpenTag doet wanneer u een agent vermeldt
OpenTag zet een @vermelding in een Slack-thread of een GitHub-issue om in een uitvoering van een coding agent op een machine die u beheert. Iemand plaatst een reactie @opentag investigate this bij een issue. Een listener ontvangt het platform-event, controleert de handtekening, koppelt de vermelding aan een gebonden project, start een coding agent op een lokale checkout en plaatst het resultaat terug in dezelfde thread.
Het project is MIT-gelicentieerd en bevindt zich op amplifthq/opentag. Per augustus 2026 is de nieuwste getagde release v0.9.0, gepubliceerd op 28 juli 2026, en deze wordt uitgebracht als een npm-pakket. Er is geen officiële container-image, dus hetgeen u vastzet is de npm-versie. Elk commando hieronder zet deze versie vast.
Dit wordt eerder een VPS-project dan een laptop-project vanwege de kant van GitHub. GitHub levert repository-events door een HTTP-verzoek te doen aan een URL die u eenmalig registreert; die URL moet dus morgen op hetzelfde adres antwoorden.
De vier bewegende onderdelen
De listener ontvangt platformevents; elk platform heeft zijn eigen listener. De GitHub-listener is een HTTP-endpoint op poort 3050 op het pad /github/webhooks. De Slack Events API-listener bevindt zich op poort 3040 op /slack/events. Slack kan ook in Socket Mode draaien, waarbij de app een uitgaande WebSocket opent en er helemaal geen inkomende poort nodig is.
De dispatcher is de coördinator. Deze luistert standaard op poort 3030, houdt de uitvoeringsstatus bij in een lokaal databasebestand dat wordt ingesteld via OPENTAG_DATABASE_PATH, en legt een audittrail vast voor elke uitvoering. Niets buiten de box mag ooit deze poort bereiken.
De runner is de lokale daemon. Deze pollt op werk, claimt een uitvoering, houdt hier een lease op vast en verstuurt standaard elke 15 seconden een heartbeat zolang de uitvoering actief is. De runner weigert elke geclaimde uitvoering waarvan het projectdoel ontbreekt of buiten de allowlist in de eigen configuratie valt. Dit is de controle die voorkomt dat een GitHub-event uw agent naar een repository wijst die u nooit heeft gekoppeld.
De executor is de coding agent zelf. OpenTag start deze via ACP (agent client protocol), een JSON-RPC-protocol dat via standard input en output verloopt. De agent draait dus als een child process binnen een werkmap die OpenTag toewijst. Ingebouwde namen zijn onder andere echo, codex, claude-code, cursor, opencode, hermes en openclaw. Begin met echo, de executor die bij de voorbeeldconfiguratie wordt geleverd, omdat hiermee wordt aangetoond dat het volledige pad werkt voordat een model uw code aanraakt.
De volgorde verandert nooit: platformevent, handtekeningcontrole, uitvoeringsrecord, claim, agent, antwoord in de thread.
Waarom een laptop en een tunnel niet volstaan
De installatiehandleiding van GitHub instrueert u om ngrok http 3050 uit te voeren en de tunnel-host in de webhook van de repository te plakken. Dat werkt de eerste tien minuten. Een gratis tunnel-host verandert telkens wanneer het proces opnieuw opstart en houdt op te bestaan zodra de laptop in de slaapstand gaat. GitHub behoudt de oude payload-URL en blijft deze proberen, waardoor het tabblad Recent Deliveries in de webhook-instellingen volloopt met foutmeldingen terwijl de thread stil blijft. Niemand merkt dit een week lang op, omdat een webhook die niets doet er precies zo uitziet als een bot waar niemand melding van maakte.
Een VPS lost de twee zaken op die voor problemen zorgen. De DNS-naam verandert niet, waardoor de payload-URL die u eenmalig plakt correct blijft. De machine gaat niet in de slaapstand, dus een reactie om 02:00 uur krijgt gewoon antwoord. Richt de server eerst correct in: de eerste tien minuten op een nieuwe VPS behandelt de inloggebruiker en de firewall waarvan deze handleiding uitgaat.
Slack vormt de uitzondering. In Socket Mode maakt het verbinding naar buiten en is er geen publieke URL nodig, waardoor een implementatie die alleen voor Slack is bedoeld gesloten kan blijven. GitHub heeft geen equivalent. Repository-webhooks zijn inkomende HTTP-verzoeken, wat betekent dat er een publiek eindpunt nodig is, en dus TLS (transport layer security) en een handtekeningcontrole.
OpenTag zelf hosten op Ubuntu vanaf een specifieke release
OpenTag v0.9.0 vereist Node.js 22 of nieuwer. Ubuntu 24.04 levert Node 18 in de eigen repository, dus installeer vanuit 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 moet v22 of hoger weergeven. Op Node 20 geeft de installatie een EBADENGINE-waarschuwing en kan de CLI falen zodra deze start.
Geef de service een eigen account. De agent draait met de rechten van deze gebruiker, dus dit mag niet uw eigen account zijn en zeker niet root. Gebruikers met minimale rechten op een VPS legt uit waarom deze scheiding de extra stap waard is.
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 moet een pad weergeven zoals /usr/bin/opentag. De linger-instelling is belangrijk op Linux: OpenTag installeert de achtergrondservice via systemd, en een gebruikersservice zonder lingering stopt zodra uw SSH-sessie wordt gesloten.
Voer de setup uit als die gebruiker.
sudo -iu opentag opentag setupDe setup vraagt zes zaken: de CLI-taal, het lokale luisteradres, de coding-agent, het lokale project om aan te werken, de op te slaan platformreferenties en de uitvoeringsmodus. Houd het luisteradres op 127.0.0.1, omdat nginx TLS-termination afhandelt en verkeer doorstuurt; de listeners hoeven daardoor nooit van buitenaf bereikbaar te zijn. Voor GitHub wordt ook gevraagd naar de repository in owner/repo-formaat, of er pull requests geopend mogen worden, de webhook-poort (standaard 3050) en het token. Kies aan het einde voor de achtergrondservicemodus. Als u al een configuratie heeft en de service zonder vragen wilt installeren, doet opentag setup --service dat.
De configuratie wordt opgeslagen in /home/opentag/.config/opentag/config.json en de runtime-status in /home/opentag/.local/state/opentag. Het is raadzaam deze sleutels handmatig te controleren nadat de setup het bestand heeft aangemaakt.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}Geef de voorkeur aan runnerToken, het bearer-token met runner-scope, boven het oudere gedeelde pairingToken. Het configuratiebestand bevat referenties in platte tekst, tenzij u deze vervangt door een geheime referentie die de waarde bij het opstarten uit de omgeving of een bestand op de schijf leest. Hoe dan ook is dit bestand het meest gevoelige onderdeel op de server: gebruik modus 600, laat het eigendom bij opentag liggen en plaats het nooit in een git-repository. Het bredere argument hiervoor staat in geheimen buiten AI-agents houden.
Controleer de installatie voordat u iets blootstelt aan het netwerk.
sudo -iu opentag opentag doctor
sudo -iu opentag opentag statusopentag doctor controleert de dispatcher, de bindings, de checkouts en de executors. opentag status toont de configuratie en runtime-status, en kan worden gefilterd op een enkele run zodra er runs bestaan. Los alle problemen op die doctor rapporteert voordat u een platform aan deze server koppelt.
Plaats TLS aan de voorzijde en stel slechts twee paden open
Nginx beëindigt TLS en stuurt exact twee paden door. Al het overige verkeer resulteert in een 404-foutmelding, waardoor een scanner die de host vindt, niets te weten komt over de achterliggende services.
Schrijf een standaard poort 80 server-blok op /etc/nginx/sites-available/opentag met de twee onderstaande locaties en laat Certbot vervolgens het TLS-gedeelte toevoegen.
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 voert syntax is ok en test is successful uit; dit is de enige barrière tussen een typefout en een herlaadactie die de site onbereikbaar maakt. Certbot op Ubuntu 24.04 met nginx behandelt vernieuwing en de manieren waarop een ACME (automatic certificate management environment) challenge kan falen. Het voltooide blok ziet er als volgt uit.
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;
}
}De = in location = /github/webhooks is een exacte match, en proxy_pass zonder toevoeging na de poort geeft de originele URI ongewijzigd door. Verwijder de =, anders wordt elk pad onder /github/webhooks/ ook doorgestuurd, wat een groter aanvalsoppervlak creëert dan nodig is voor de listener.
De firewall blijft beperkt.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw statusPoorten 3030, 3040 en 3050 worden nooit geopend. Bevestig dat deze gebonden zijn aan loopback in plaats van aan alle interfaces.
sudo ss -tlnpElke OpenTag-regel moet 127.0.0.1:3030 of iets dergelijks bevatten. Een regel met 0.0.0.0:3050 betekent dat de listener zich aanbiedt aan het gehele internet en dat alleen ufw dit tegenhoudt; één firewallfout is dan voldoende voor een open agent-trigger. Basisprincipes van ufw firewall legt uit wat die standaard 'deny'-instelling werkelijk doet.
Twee controles bewijzen de werking van de voordeur. curl -I https://opentag.example.com/ geeft 404 terug vanuit nginx, wat aantoont dat het certificaat geldig is en de catch-all gesloten is. Een verzoek aan /slack/events of /github/webhooks zonder handtekening mag nooit een 200-statuscode retourneren.
Verifieer elke handtekening, aangezien de URL openbaar is
Iedereen kan de payload-URL vinden. Deze staat in uw repository-instellingen, in de browsergeschiedenis of in een screenshot dat in een ticket is geplakt. De handtekening is het enige dat een echte GitHub-aflevering onderscheidt van een verzoek dat iemand handmatig heeft getypt.
GitHub ondertekent elke aflevering met het webhook-geheim en stuurt het resultaat in de x-hub-signature-256-header. OpenTag verifieert die header tegen platforms.github.webhookSecret. De hardening-notities van het project stellen de regel direct: accepteer geen niet-ondertekende brongebeurtenissen op /github/webhooks. Slack ondertekent elk verzoek met SLACK_SIGNING_SECRET en voegt een tijdstempel toe, zodat een onderschepte body niet uren later opnieuw kan worden afgespeeld.
Dit overslaan is geen klein risico. Een niet-geverifieerd eindpunt accepteert een handgeschreven issue_comment-payload met @opentag, waarna OpenTag een coding agent uitvoert, met uw token, in uw checkout, op basis van instructies van een vreemde. Het antwoord gaat naar de thread die de valse payload aangeeft.
OpenTag voegt twee lagen toe. Bronafleveringen worden bijgehouden via een afleverings-ID, zodat het opnieuw afleveren van dezelfde gebeurtenis geen tweede run start. Runner-aanroepen accepteren idempotency-keys, zodat het opnieuw afspelen van een verzoek succes retourneert zonder een extra audit-gebeurtenis toe te voegen.
Rate limits zijn configureerbaar en moeten ingeschakeld zijn. OPENTAG_RATE_LIMIT_WINDOW_MS en OPENTAG_RATE_LIMIT_MAX_REQUESTS begrenzen de verzoekfrequentie, OPENTAG_MAX_REQUEST_BODY_BYTES begrenst de body, en een te grote payload wordt geweigerd met 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true bestaat voor lokale ontwikkeling en hoort niet thuis op een openbare server. Nog een regel uit dezelfde notities: een openbare relay-URL moet HTTPS gebruiken, en de CLI staat platte HTTP alleen toe voor localhost.
Welke token-scopes heeft de bot daadwerkelijk nodig?
Op GitHub gebruikt OpenTag een fine-grained personal access token in plaats van een GitHub App. De documentatie vermeldt dat het App-pad gepland staat en momenteel niet de standaard CLI-configuratie is. Dit heeft een consequentie die vaak over het hoofd wordt gezien: de bot plaatst reacties namens de persoon die het token heeft aangemaakt. Maak het token aan onder een account waarvan u het acceptabel vindt dat deze in elk triage-antwoord wordt geciteerd.
Beperk de scope zo strikt als de installatiehandleiding voorschrijft. Kies Only select repositories en selecteer er één. Verleen Issues: Read and write en Pull requests: Read and write. Dit is voldoende om een vermelding te lezen en in de thread te reageren.
Let op wat ontbreekt: schrijftoegang tot code. OpenTag pusht geen branches tenzij preparePullRequestBranch op true is ingesteld, en er bestaat een apart githubApplyToken zodat het token dat code schrijft niet hetzelfde is als het token dat reacties plaatst. Houd deze gescheiden en activeer het schrijf-token pas nadat het lees-en-reageer-pad enkele weken heeft gefunctioneerd.
De configuratie die u moet vermijden, is een token met Contents: Read and write voor All repositories. Iedereen die op een van die repositories kan reageren, kan nu een agent aansturen die over commit-rechten beschikt, waarbij het audit-spoor aangeeft dat de eigenaar van het token dit heeft gedaan. Verruim de scope per repository, nadat de agent dit heeft verdiend.
Op Slack zijn de bot-scopes app_mentions:read, chat:write, reactions:write en channels:history. Privékanalen vereisen daarnaast groups:history plus een abonnement op het message.groups event. Socket Mode vereist een app-level token met connections:write, het token dat begint met xapp-. channels:history leest de berichtgeschiedenis in de openbare kanalen waaraan de bot is toegevoegd; voeg de bot daarom alleen toe aan de kanalen waar deze gewenst is, in plaats van overal.
Route één issue van begin tot eind
De webhook komt eerst. Open in de repository Settings, vervolgens Webhooks en daarna Add webhook. De payload URL is https://opentag.example.com/github/webhooks, het content type is application/json en het secret is het exemplaar dat de setup heeft gegenereerd. Abonneer u op Issue comments en Pull request review comments, en niets anders.
GitHub verstuurt direct een ping-delivery zodra u opslaat. Open Recent Deliveries en controleer of het verzoek de server überhaupt heeft bereikt. Een 502-foutmelding betekent dat nginx de listener niet kon bereiken; dit is een lokaal probleem, geen probleem bij GitHub.
Gebruik het nu. Open een issue waarin een bug wordt beschreven en plaats een comment:
@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.Wat er in volgorde zou moeten gebeuren: Recent Deliveries registreert de issue_comment-delivery met een 2xx-respons. De dispatcher registreert een run. De runner claimt deze en begint met het versturen van heartbeats. De executor opent de checkout en gaat aan het werk. Het antwoord verschijnt als een comment in dezelfde issue-thread. sudo -iu opentag opentag status toont de run terwijl deze actief is, zodat u deze kunt volgen in plaats van te gissen.
Zet approvalMode op ask vóór de eerste echte run. In de ask-modus pauzeert de run en wacht deze op een persoon voordat er acties worden ondernomen die de status wijzigen. De auto- en autonomous-modi bestaan ook en zijn later zinvol, zodra u een maand aan transcripten in een repository hebt gelezen.
Aan de kant van Slack begint dezelfde run met /bind owner/repo in het kanaal, gevolgd door een mention. De bot beantwoordt ook /help, /status, /doctor, /stop en /unbind confirm. Beperk wie bindingen mag wijzigen met OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, een door komma's gescheiden lijst met Slack-gebruikers-ID's, omdat een binding de koppeling is tussen een openbaar kanaal en een checkout op uw server.
Triage is een goede eerste route omdat deze alleen leest en niet schrijft, en het antwoord eenvoudig te beoordelen is. Review is de volgende stap, waarbij de agent commentaar geeft op een diff in plaats van op een issue: een self-hosted pull request review agent is dezelfde architectuur gericht op pull requests. Als u wilt dat de agent tijdens het werk uw eigen systemen kan bereiken, is dat de taak van MCP servers op een VPS. Zoeken op het web is de andere functionaliteit waar triage steeds om vraagt, en het koppelen van de agent aan uw eigen SearXNG-instantie houdt die zoekopdrachten op hardware die u zelf beheert, tegen de prijs van één extra kanaal waarover tekst van een vreemde de agent bereikt.
Wat gebeurt er als de agent zich publiekelijk vergist?
De agent zal fouten maken. De vraag is wat de kosten daarvan zijn.
Een onjuist antwoord op een publiek issue is een reactie onder een naam die uw team herkent, en GitHub verstuurt dit direct per e-mail naar iedereen die geabonneerd is op het moment van plaatsen. Het verwijderen van de reactie trekt de e-mail niet in. Hetzelfde geldt voor een Slack-notificatie. Houd er rekening mee dat het antwoord in het openbaar fout kan zijn, in plaats van te vertrouwen op een correct antwoord in besloten kring.
Vier keuzes beperken de schade, en deze zijn belangrijker dan elke prompt die u schrijft.
- Draai in de
ask-modus, zodat de agent een voorstel doet, een persoon dit goedkeurt en een foutief plan slechts één klik kost. - Laat
preparePullRequestBranchop de standaardwaarde false staan, zodat het ergste resultaat van een mislukte run een onjuiste reactie is in plaats van een onjuiste branch. - Koppel in het begin één repository aan één kanaal. De runner weigert elke run waarvan het doelproject buiten de lokale allowlist valt, waardoor een niet-gekoppelde repository de agent niet kan aansturen.
- Houd het token voor reacties gescheiden van elk token voor het doorvoeren van wijzigingen, zodat het intrekken van schrijfrechten niet direct het triagesysteem platlegt.
Slack heeft een /stop-commando voor een run die de verkeerde kant op gaat. Elke run laat bovendien een audit-log achter met de vermelding die de run startte en wat de agent heeft uitgevoerd; dit is wat u achteraf leest om te achterhalen waar het misging.
Het sociale aspect is net zo belangrijk als de configuratie. Plaats de bot in een kanaal waar mensen een machine verwachten en weten dat deze fouten kan maken. Een zelfverzekerd, foutief antwoord in een kanaal met veertig mensen die aannemen dat een mens het heeft gecontroleerd, kost meer dan de tijd die met triage is bespaard. Vermeld in de kanaalomschrijving wie de eigenaar is van de bot en wie de output controleert.
Backups, upgrades en de pin
Twee paden bevatten alle gegevens: /home/opentag/.config/opentag/config.json en /home/opentag/.local/state/opentag. Het eerste pad bevat uw inloggegevens, het tweede bevat de uitvoeringsgeschiedenis en het databasebestand. Maak van beide een back-up met modus 600 en bewaar deze buiten de server. Het verlies hiervan betekent dat u tokens en koppelingen opnieuw moet aanmaken, niet dat u de server opnieuw hoeft op te bouwen.
Upgrades bestaan uit een versie-update en een herstart.
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 doctorPin de versie in plaats van @latest te volgen. Deze software voert een coding agent uit op uw repository met een actief token; een release die 's nachts wordt gepubliceerd, is dus een ongecontroleerde wijziging. Het beveiligingsbeleid backport niets en fixes verschijnen alleen in de nieuwste release. Pinnen betekent daarom dat u de changelog leest en bewust overstapt. Het betekent niet dat u voor altijd op v0.9.0 blijft. De geschiedenis tot juli 2026 laat zien dat er meerdere releases per maand zijn; dit is een goede reden om voor elke update de release notes te lezen.
FAQ
Heb ik een VPS nodig om OpenTag te draaien, of volstaat een laptop?
Een laptop volstaat voor Slack, omdat Socket Mode een uitgaande WebSocket opent en geen inkomende poort vereist. Voor GitHub ligt dit anders. Repository webhooks leveren gegevens af via inkomende HTTP naar een URL die u eenmalig registreert; het adres moet dus constant blijven en bereikbaar zijn terwijl u slaapt. Een tunnel-host van een gratis account wijzigt bij elke herstart, waardoor GitHub naar het oude adres blijft sturen. Dit resulteert in mislukte pogingen in het tabblad Recent Deliveries van de repository en stilte in de thread. Een VPS met een vaste DNS-naam en een certificaat lost beide problemen op.
Welke GitHub-rechten heeft OpenTag nodig?
Een fine-grained personal access token beperkt tot Only select repositories, met Issues: Read and write en Pull requests: Read and write. Dit is voldoende om vermeldingen te lezen en te reageren in de thread. Schrijftoegang tot code is niet nodig, tenzij u preparePullRequestBranch op true zet zodat OpenTag branches pusht. Er bestaat een aparte githubApplyToken zodat het token voor codewijzigingen gescheiden blijft van het token voor commentaar. Vermijd een token voor alle repositories met contents write, omdat iedereen die op een van die repositories kan reageren, anders een agent zou kunnen aansturen die commits kan uitvoeren.
Hoe stop ik een run die niet goed verloopt?
Slack heeft hiervoor het commando /stop. Op de server toont opentag status wat er momenteel draait, en opentag service stop stopt de daemon, wat de gehele pipeline beëindigt in plaats van slechts één run. Om beide te vermijden, stelt u approvalMode in op ask zodat runs pauzeren voor menselijke tussenkomst voordat er wijzigingen worden doorgevoerd. Laat preparePullRequestBranch op false staan zodat een foutieve run een commentaar genereert in plaats van een branch.
Waarom geeft mijn webhook een 502-foutmelding terwijl de thread stil blijft?
Een 502-foutmelding is afkomstig van nginx, niet van OpenTag, en betekent dat de proxy de listener niet kon bereiken. /var/log/nginx/error.log zal connect() failed (111: Connection refused) while connecting to upstream tonen. Of de listener is gestopt, of deze draait op een andere poort dan in de regel proxy_pass is opgegeven. Voer sudo ss -tlnp uit en bevestig dat er iets luistert op 127.0.0.1:3050 voor GitHub en 127.0.0.1:3040 voor Slack, en voer vervolgens opentag doctor uit voor de bindings en executors.