OpenTag 코딩 에이전트 VPS 직접 호스팅 가이드
Slack 및 GitHub 멘션에 반응하는 OpenTag를 VPS에 배포하는 방법을 설명합니다. TLS 인그레스 설정, 웹훅 서명 검증, 토큰 스코프 구성 및 안전한 기본값 설정을 포함하여 v0.9.0 버전 기준으로 상세히 안내합니다.
에이전트를 언급할 때 OpenTag가 수행하는 작업
OpenTag는 Slack 스레드나 GitHub 이슈에서 @mention을 사용하면 사용자가 소유한 머신에서 코딩 에이전트를 실행합니다. 누군가 이슈에 @opentag investigate this라고 댓글을 남기면, 리스너가 플랫폼 이벤트를 수신하고 서명을 확인합니다. 이후 언급된 내용을 바인딩된 프로젝트와 대조하고, 로컬 체크아웃 환경에서 코딩 에이전트를 시작한 뒤 결과를 동일한 스레드에 게시합니다.
이 프로젝트는 MIT 라이선스를 따르며 amplifthq/opentag에서 확인할 수 있습니다. 2026년 8월 기준으로 최신 태그된 릴리스는 2026년 7월 28일에 게시된 v0.9.0이며, npm 패키지로 배포됩니다. 공식 컨테이너 이미지는 제공되지 않으므로 npm 버전을 고정하여 사용해야 합니다. 아래의 모든 명령어는 해당 버전을 고정합니다.
GitHub 연동 방식 때문에 이 프로젝트는 노트북보다는 VPS에서 운영하는 것이 적합합니다. GitHub는 사용자가 한 번 등록한 URL로 HTTP 요청을 보내 리포지토리 이벤트를 전달하므로, 해당 URL은 다음 날에도 동일한 주소에서 응답할 수 있어야 합니다.
4가지 핵심 구성 요소
리스너(Listener)는 플랫폼 이벤트를 수신하며, 각 플랫폼마다 고유한 리스너가 있습니다. GitHub 리스너는 포트 3050의 /github/webhooks 경로에서 동작하는 HTTP 엔드포인트입니다. Slack Events API 리스너는 포트 3040의 /slack/events에서 동작합니다. Slack은 소켓 모드(Socket Mode)로도 실행할 수 있는데, 이 경우 애플리케이션이 아웃바운드 WebSocket을 열기 때문에 인바운드 포트가 전혀 필요하지 않습니다.
디스패처(Dispatcher)는 조정자 역할을 합니다. 기본적으로 포트 3030에서 대기하며, OPENTAG_DATABASE_PATH로 설정된 로컬 데이터베이스 파일에 실행 상태를 유지하고 모든 실행에 대한 감사 추적(audit trail)을 기록합니다. 외부의 그 어떤 요소도 이 포트에 접근해서는 안 됩니다.
러너(Runner)는 로컬 데몬입니다. 작업을 폴링(polling)하여 실행을 요청하고, 해당 실행에 대한 임대(lease)를 유지하며, 실행이 활성화된 동안 기본적으로 15초마다 하트비트를 전송합니다. 러너는 프로젝트 대상이 누락되었거나 설정의 허용 목록(allowlist) 외부에 있는 경우 해당 실행 요청을 거부합니다. 이는 GitHub 이벤트가 사용자가 바인딩하지 않은 저장소로 에이전트를 가리키는 것을 방지하는 검사 과정입니다.
실행기(Executor)는 코딩 에이전트 그 자체입니다. OpenTag는 표준 입출력을 통해 통신하는 JSON-RPC 프로토콜인 ACP(agent client protocol)를 사용하여 실행기를 시작하며, 에이전트는 OpenTag가 지정한 작업 디렉터리 내에서 자식 프로세스로 실행됩니다. 내장된 이름으로는 echo, codex, claude-code, cursor, opencode, hermes, openclaw이 있습니다. 예제 설정에 포함된 실행기인 echo로 시작하십시오. 이는 모델이 코드를 다루기 전에 전체 경로가 정상적으로 작동하는지 확인하는 용도입니다.
처리 순서는 항상 동일합니다: 플랫폼 이벤트 발생, 서명 확인, 실행 기록, 요청, 에이전트 실행, 스레드 내 응답.
노트북과 터널만으로는 부족한 이유
GitHub 설정 가이드는 ngrok http 3050을 실행하고 터널 호스트를 리포지토리 웹훅에 붙여넣으라고 안내합니다. 이는 처음 10분 동안만 유효합니다. 무료 터널 호스트는 프로세스가 재시작될 때마다 주소가 바뀌며, 노트북이 절전 모드에 들어가면 연결이 끊깁니다. GitHub는 이전 페이로드 URL을 그대로 유지하며 계속 요청을 보내므로, 웹훅 설정의 Recent Deliveries 탭은 실패 기록으로 가득 차게 되고 스레드는 아무런 반응을 보이지 않습니다. 웹훅이 작동하지 않아도 아무도 언급하지 않는 봇과 똑같이 보이기 때문에, 일주일이 지나도록 아무도 이 사실을 알아차리지 못합니다.
VPS를 사용하면 문제가 되는 두 가지 사항이 해결됩니다. DNS 이름이 바뀌지 않으므로 한 번 입력한 페이로드 URL은 계속 유효합니다. 서버는 절전 모드에 들어가지 않으므로 새벽 02:00에 달린 댓글에도 즉시 응답할 수 있습니다. 먼저 서버를 올바르게 설정하십시오. 새 VPS에서의 처음 10분 가이드에서 이 문서가 전제로 하는 로그인 사용자 설정과 방화벽 구성을 다룹니다.
Slack은 예외입니다. Socket Mode를 사용하면 외부로 연결을 시도하므로 공개 URL이 필요 없으며, Slack 전용 배포 환경은 외부 포트를 열지 않아도 됩니다. GitHub에는 이에 대응하는 기능이 없습니다. 리포지토리 웹훅은 인바운드 HTTP 요청을 사용하므로 공개 엔드포인트가 필요하며, 이는 곧 TLS(전송 계층 보안)와 서명 검증이 필요함을 의미합니다.
Ubuntu에서 고정 릴리스를 사용하여 OpenTag 셀프 호스팅하기
OpenTag v0.9.0은 Node.js 22 이상을 요구합니다. Ubuntu 24.04는 자체 저장소에서 Node 18을 제공하므로 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는 v22 이상의 버전을 출력해야 합니다. Node 20에서 설치를 진행하면 EBADENGINE 경고가 발생하며, CLI 실행 시 오류가 발생할 수 있습니다.
서비스 전용 계정을 생성하십시오. 에이전트는 해당 사용자의 권한으로 실행되므로, 로그인 계정이나 root 계정을 사용해서는 안 됩니다. VPS의 최소 권한 사용자에서 이러한 분리가 왜 필요한지 설명합니다.
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은 /usr/bin/opentag와 같은 경로를 출력해야 합니다. Linux에서 linger 설정은 중요합니다. OpenTag는 systemd를 통해 백그라운드 서비스를 설치하는데, linger가 설정되지 않은 사용자 서비스는 SSH 세션이 종료되는 즉시 중단되기 때문입니다.
해당 사용자 권한으로 설정을 진행하십시오.
sudo -iu opentag opentag setup설정 과정에서 CLI 언어, 로컬 수신 주소, 코딩 에이전트, 작업할 로컬 프로젝트, 저장할 플랫폼 자격 증명, 실행 방식 등 6가지를 묻습니다. 수신 주소는 127.0.0.1로 유지하십시오. Nginx가 TLS를 종료하고 해당 주소로 전달하므로, 수신기가 외부에서 직접 접근 가능할 필요는 없습니다. GitHub의 경우 owner/repo 형식의 저장소 이름, 풀 리퀘스트 생성 허용 여부, 웹훅 포트(기본값 3050), 토큰을 추가로 묻습니다. 마지막 단계에서 백그라운드 서비스 모드를 선택하십시오. 이미 설정 파일이 있고 프롬프트 없이 서비스를 설치하려면 opentag setup --service을 사용하십시오.
설정 파일은 /home/opentag/.config/opentag/config.json에 저장되며, 런타임 상태는 /home/opentag/.local/state/opentag에 위치합니다. 설정 파일이 생성된 후에는 직접 내용을 확인하는 것이 좋습니다.
{
"runnerId": "runner_local",
"dispatcherUrl": "http://localhost:3030",
"runnerToken": "...",
"approvalMode": "ask",
"repositories": []
}기존의 공유 pairingToken 대신 러너 범위의 베어러 토큰인 runnerToken를 사용하는 것을 권장합니다. 설정 파일은 자격 증명을 일반 텍스트로 저장합니다. 이를 방지하려면 환경 변수나 디스크의 파일에서 값을 읽어오는 비밀 참조 방식으로 교체하십시오. 어떤 방식이든 이 파일은 서버에서 가장 민감한 정보이므로, 권한을 600으로 설정하고 소유자를 opentag으로 지정해야 하며, 절대 git 저장소 내부에 두어서는 안 됩니다. 이에 대한 자세한 논의는 AI 에이전트에서 비밀 정보 보호하기를 참조하십시오.
외부에 서비스를 노출하기 전에 설치 상태를 점검하십시오.
sudo -iu opentag opentag doctor
sudo -iu opentag opentag statusopentag doctor은 디스패처, 바인딩, 체크아웃, 실행기를 확인합니다. opentag status은 설정과 런타임 상태를 출력하며, 실행 기록이 있다면 특정 실행 단위로 범위를 좁힐 수 있습니다. doctor가 보고하는 모든 문제를 해결한 뒤에 플랫폼을 이 서버에 연결하십시오.
TLS를 전면에 배치하고 두 개의 경로만 개방하기
nginx에서 TLS를 종료하고 정확히 두 개의 경로만 전달합니다. 그 외의 모든 요청은 404를 반환하므로, 호스트를 스캔하는 공격자는 배후에서 어떤 서비스가 실행 중인지 알 수 없습니다.
/etc/nginx/sites-available/opentag에 아래 두 개의 location을 포함한 일반적인 포트 80 서버 블록을 작성한 뒤, Certbot을 사용하여 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은 syntax is ok와 test is successful을 출력하며, 오타로 인해 사이트가 중단되는 것을 방지하는 유일한 안전장치입니다. Ubuntu 24.04에서 nginx와 Certbot 사용하기에서는 갱신 방법과 ACME(자동 인증서 관리 환경) 챌린지가 실패하는 원인들을 다룹니다. 완성된 블록은 다음과 같습니다.
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;
}
}location = /github/webhooks의 =는 정확히 일치하는 경로를 의미하며, 포트 뒤에 아무것도 없는 proxy_pass은 원래의 URI를 변경 없이 그대로 전달합니다. =을 제거하면 /github/webhooks/ 하위의 모든 경로가 전달되는데, 이는 리스너가 필요로 하는 것보다 더 넓은 공격 표면을 노출하게 됩니다.
방화벽은 좁게 유지합니다.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status포트 3030, 3040, 3050은 절대 개방하지 않습니다. 해당 포트들이 모든 인터페이스가 아닌 루프백(loopback)에 바인딩되었는지 확인하십시오.
sudo ss -tlnp모든 OpenTag 라인은 127.0.0.1:3030 또는 이와 유사해야 합니다. 0.0.0.0:3050으로 표시된 라인은 리스너가 전체 인터넷에 노출되어 있음을 의미하며, 오직 ufw만이 이를 막고 있는 상태입니다. 이는 방화벽 설정 실수 한 번으로 에이전트가 외부로 노출될 수 있는 위험한 상황입니다. ufw 방화벽 기초에서 기본 거부(default deny) 정책이 실제로 어떤 역할을 하는지 설명합니다.
두 가지 확인 절차로 정문을 검증합니다. curl -I https://opentag.example.com/은 nginx로부터 404를 반환해야 하며, 이는 인증서가 유효하고 catch-all 설정이 차단되었음을 보여줍니다. 서명이 없는 /slack/events 또는 /github/webhooks로의 요청은 절대 200을 반환해서는 안 됩니다.
모든 서명을 검증하십시오. URL은 공개되어 있습니다.
누구나 페이로드 URL을 찾을 수 있습니다. 이 URL은 저장소 설정, 브라우저 기록, 티켓에 붙여넣은 스크린샷 등에 노출되어 있습니다. 서명은 실제 GitHub 전달과 누군가 수동으로 입력한 요청을 구분하는 유일한 수단입니다.
GitHub는 각 전달 내용을 webhook secret으로 서명하고 그 결과를 x-hub-signature-256 헤더에 담아 보냅니다. OpenTag는 해당 헤더를 platforms.github.webhookSecret와 대조하여 검증합니다. 프로젝트의 보안 강화 지침은 이 규칙을 명확히 명시합니다. /github/webhooks에서 서명되지 않은 소스 이벤트를 수락하지 마십시오. Slack은 SLACK_SIGNING_SECRET로 각 요청에 서명하고 타임스탬프를 포함하므로, 캡처된 본문이 몇 시간 뒤에 재전송(replay)될 수 없습니다.
이를 생략하는 것은 결코 작은 위험이 아닙니다. 검증되지 않은 엔드포인트는 수동으로 작성된 issue_comment 페이로드를 수락하며, 여기에는 @opentag가 포함될 수 있습니다. 그러면 OpenTag는 낯선 이의 지시에 따라 귀하의 토큰을 사용하여 귀하의 체크아웃 환경에서 코딩 에이전트를 실행합니다. 응답은 가짜 페이로드가 지정한 스레드로 전송됩니다.
OpenTag는 여기에 두 가지 계층을 추가합니다. 소스 전달은 delivery ID로 추적되므로, 동일한 이벤트를 재전송해도 두 번째 실행이 시작되지 않습니다. 러너 호출은 멱등성 키(idempotency keys)를 수락하므로, 동일한 요청을 재전송해도 추가적인 감사 이벤트 없이 성공 응답만 반환합니다.
속도 제한(Rate limits)은 설정 가능하며 반드시 활성화해야 합니다. OPENTAG_RATE_LIMIT_WINDOW_MS와 OPENTAG_RATE_LIMIT_MAX_REQUESTS는 요청 속도를 제한하고, OPENTAG_MAX_REQUEST_BODY_BYTES는 본문 크기를 제한하며, 크기가 초과된 페이로드는 413 request_body_too_large와 함께 거부됩니다. OPENTAG_RATE_LIMIT_DISABLED=true는 로컬 개발을 위한 것이며, 공개 서버에는 절대 사용해서는 안 됩니다. 동일한 지침에 명시된 또 하나의 규칙은 공개 릴레이 URL은 반드시 HTTPS를 사용해야 한다는 점이며, CLI는 localhost에서만 평문 HTTP를 허용합니다.
봇은 실제로 어떤 토큰 스코프가 필요한가?
GitHub에서 OpenTag는 GitHub App 대신 세분화된 개인 액세스 토큰(fine-grained personal access token)을 사용합니다. 문서에 따르면 App 방식은 향후 계획된 기능이며 현재 기본 CLI 설정이 아닙니다. 이로 인해 사용자가 간과하는 결과가 발생하는데, 봇이 토큰을 생성한 사람의 계정으로 댓글을 작성한다는 점입니다. 모든 트리아지(triage) 답변에 해당 계정 이름이 표시되어도 괜찮은 계정으로 토큰을 생성하십시오.
설정 가이드에 명시된 대로 스코프를 최대한 좁게 제한하십시오. Only select repositories를 선택하고 저장소를 하나만 지정합니다. Issues: Read and write와 Pull requests: Read and write 권한을 부여하십시오. 이것만으로도 멘션을 읽고 스레드에 답변하기에 충분합니다.
누락된 권한에 주목하십시오. 코드에 대한 쓰기 권한은 포함되지 않습니다. preparePullRequestBranch이 true로 설정되지 않는 한 OpenTag는 브랜치를 푸시하지 않으며, 코드를 작성하는 토큰과 댓글을 작성하는 토큰이 분리되도록 별도의 githubApplyToken이 존재합니다. 이 둘을 분리하여 관리하고, 읽기 및 댓글 작성 경로가 몇 주간 문제없이 작동할 때까지 쓰기 토큰은 비활성화해 두십시오.
피해야 할 설정은 All repositories에 대해 Contents: Read and write 권한을 가진 토큰을 사용하는 것입니다. 해당 저장소에 댓글을 달 수 있는 모든 사용자가 이제 커밋 권한을 가진 에이전트를 조종할 수 있게 되며, 감사 로그에는 토큰 소유자가 작업을 수행한 것으로 기록됩니다. 에이전트의 신뢰도가 검증된 후, 저장소를 하나씩 추가하며 스코프를 넓히십시오.
Slack에서 봇 스코프는 app_mentions:read, chat:write, reactions:write, channels:history입니다. 비공개 채널의 경우 groups:history 권한과 message.groups 이벤트 구독이 추가로 필요합니다. Socket Mode를 사용하려면 connections:write 권한이 포함된 앱 레벨 토큰이 필요하며, 이는 xapp-로 시작하는 토큰입니다. channels:history은 봇이 추가된 공개 채널의 메시지 기록을 읽으므로, 모든 곳에 추가하기보다는 봇이 필요한 채널에만 추가하십시오.
이슈 처리 경로를 처음부터 끝까지 구성하기
웹훅이 가장 먼저 필요합니다. 저장소에서 Settings, Webhooks, Add webhook 순으로 이동합니다. Payload URL은 https://opentag.example.com/github/webhooks로, Content type은 application/json로 설정하고, Secret은 설정 과정에서 생성한 값을 입력합니다. Issue comments와 Pull request review comments만 구독하고 나머지는 선택하지 않습니다.
저장하는 즉시 GitHub에서 핑(ping) 요청을 보냅니다. Recent Deliveries를 열어 요청이 서버에 도달했는지 확인합니다. 502 오류가 발생한다면 Nginx가 리스너에 연결하지 못한 것이며, 이는 GitHub의 문제가 아니라 로컬 환경의 문제입니다.
이제 기능을 사용해 봅니다. 버그를 설명하는 이슈를 열고 다음과 같이 댓글을 작성합니다.
@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.이후 순차적으로 다음 과정이 진행되어야 합니다. Recent Deliveries에 issue_comment 전달 내역이 2xx 응답과 함께 기록됩니다. 디스패처가 실행을 기록하고, 러너가 이를 할당받아 하트비트를 시작합니다. 실행기가 체크아웃을 수행하고 작업을 진행합니다. 결과는 동일한 이슈 스레드에 댓글로 달립니다. sudo -iu opentag opentag status을 확인하면 실행 중인 작업을 볼 수 있으므로, 추측할 필요 없이 진행 상황을 모니터링할 수 있습니다.
첫 번째 실제 실행 전에는 approvalMode을 ask로 설정하십시오. ask 모드에서는 상태를 변경하는 작업을 수행하기 전에 실행이 일시 중지되고 사용자의 승인을 기다립니다. auto 및 autonomous 모드도 존재하며, 한 달 정도 기록을 검토한 저장소라면 나중에 사용하기에 적절합니다.
Slack 측에서는 동일한 실행이 채널 내 /bind owner/repo으로 시작되며, 이후 멘션이 이어집니다. 봇은 /help, /status, /doctor, /stop 및 /unbind confirm에도 응답합니다. 바인딩은 공개 채널과 서버의 체크아웃을 연결하는 매핑이므로, OPENTAG_SLACK_BINDING_ADMIN_USER_IDS에 쉼표로 구분된 Slack 사용자 ID 목록을 입력하여 바인딩 변경 권한을 제한하십시오.
트리아지(Triage)는 읽기 전용 작업이며 결과 확인이 쉬우므로 첫 번째 경로로 적합합니다. 다음 단계는 리뷰(Review)이며, 에이전트가 이슈가 아닌 diff에 댓글을 작성합니다. 자체 호스팅 풀 리퀘스트 리뷰 에이전트는 이와 동일한 아키텍처를 풀 리퀘스트에 적용한 것입니다. 에이전트가 작업 중 내부 시스템에 접근해야 한다면 VPS의 MCP 서버를 사용하십시오. 트리아지에서 자주 요청하는 또 다른 기능은 웹 검색이며, 자체 SearXNG 인스턴스 연결을 통해 외부인의 텍스트가 에이전트에 전달되는 경로를 하나 더 추가하는 대신, 검색 조회를 직접 운영하는 하드웨어에서 처리할 수 있습니다.
에이전트가 모두가 보는 앞에서 틀린 답을 내놓으면 어떻게 됩니까?
틀릴 수 있습니다. 문제는 그로 인한 비용입니다.
공개된 이슈에 잘못된 답변이 달리면 팀이 인식하는 이름으로 댓글이 작성되며, GitHub는 게시되는 즉시 구독 중인 모든 사람에게 이메일을 발송합니다. 댓글을 삭제해도 이메일은 회수되지 않습니다. Slack 알림도 마찬가지입니다. 비공개 환경에서 정답을 맞히는 것보다, 공개된 환경에서 틀린 답변이 나갈 상황을 대비하십시오.
피해를 제한하는 네 가지 선택지가 있으며, 이는 어떤 프롬프트보다 중요합니다.
ask모드로 실행하여 에이전트가 제안하고 사람이 승인하게 하십시오. 잘못된 계획이라도 클릭 한 번으로 막을 수 있습니다.preparePullRequestBranch를 기본값인 false로 유지하십시오. 잘못된 실행의 최악의 결과가 잘못된 브랜치가 아닌 잘못된 댓글이 되도록 하기 위함입니다.- 시작할 때는 하나의 저장소와 하나의 채널만 연결하십시오. 러너는 프로젝트 대상이 로컬 허용 목록 외부에 있으면 실행을 거부하므로, 연결되지 않은 저장소는 에이전트를 끌어들일 수 없습니다.
- 댓글 작성 토큰과 적용 토큰을 분리하십시오. 쓰기 권한을 취소해도 트리아지(triage) 기능까지 중단되지 않게 하기 위함입니다.
Slack에는 잘못된 방향으로 진행 중인 실행을 위한 /stop 명령어가 있습니다. 모든 실행은 시작을 유발한 멘션과 에이전트가 수행한 작업을 기록으로 남깁니다. 나중에 어디서 잘못되었는지 파악하려면 이 감사 기록을 읽어야 합니다.
사회적 측면도 설정만큼 중요합니다. 봇을 기계가 있다는 것을 인지하고 오류 가능성을 알고 있는 채널에 배치하십시오. 사람이 검토했다고 가정하는 40명의 채널에서 자신 있게 틀린 답변을 내놓는 것은 트리아지로 절약한 시간보다 더 큰 비용을 치르게 합니다. 채널 설명에 봇의 소유자가 누구인지, 누가 출력 결과를 확인하는지 명시하십시오.
백업, 업그레이드 및 버전 고정
모든 데이터는 /home/opentag/.config/opentag/config.json과 /home/opentag/.local/state/opentag 두 경로에 저장됩니다. 첫 번째 경로에는 자격 증명이, 두 번째 경로에는 실행 기록과 데이터베이스 파일이 포함되어 있습니다. 두 경로 모두 600 모드로 백업하여 서버 외부의 안전한 곳에 보관하십시오. 이 파일들을 분실하면 서버를 다시 구축하는 것이 아니라 토큰과 바인딩을 모두 새로 생성해야 합니다.
업그레이드는 버전 번호를 올리고 서비스를 재시작하는 과정으로 진행됩니다.
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 doctor@latest을 추적하는 대신 버전을 고정하십시오. 이 소프트웨어는 라이브 토큰을 사용하여 저장소에 대해 코딩 에이전트를 실행하므로, 밤사이에 배포된 릴리스는 검토되지 않은 변경 사항이 될 수 있습니다. 보안 정책상 이전 버전에 대한 백포트는 제공되지 않으며, 수정 사항은 최신 릴리스에만 적용됩니다. 따라서 버전을 고정한다는 것은 변경 로그를 읽고 의도적으로 업데이트를 수행한다는 의미입니다. 이는 v0.9.0 버전에 영원히 머물러야 한다는 뜻이 아닙니다. 2026년 7월까지의 기록을 보면 한 달에 여러 번 릴리스가 이루어지므로, 업데이트를 수행하기 전에 릴리스 노트를 읽는 것이 좋습니다.
FAQ
Do I need a VPS to run OpenTag, or is a laptop enough?
A laptop is enough for Slack alone, because Socket Mode opens an outbound WebSocket and needs no inbound port. GitHub is different. Repository webhooks deliver over inbound HTTP to a URL you register once, so the address must stay the same and must answer while you sleep. A tunnel host from a free account changes on every restart, and GitHub keeps posting to the old one, which shows up as failed entries in the repository Recent Deliveries tab and as silence in the thread. A VPS with a fixed DNS name and a certificate removes both problems.
Which GitHub permissions does OpenTag need?
A fine-grained personal access token limited to Only select repositories, with Issues: Read and write and Pull requests: Read and write. That covers reading a mention and replying in the thread. Write access to code is not needed unless you set preparePullRequestBranch to true so OpenTag pushes branches, and a separate githubApplyToken exists so the code-writing token stays apart from the commenting one. Avoid an all-repositories token with contents write, because anyone who can comment on any of those repositories could then steer an agent that can commit.
How do I stop a run that is going wrong?
Slack has a /stop command for exactly this. On the server, opentag status shows what is running, and opentag service stop stops the daemon, which ends the whole pipeline rather than one run. To avoid needing either, set approvalMode to ask so runs pause for a person before they change anything, and leave preparePullRequestBranch at false so a bad run produces a comment instead of a branch.
Why does my webhook return 502 while the thread stays silent?
502 comes from nginx, not from OpenTag, and it means the proxy could not reach the listener. /var/log/nginx/error.log will show connect() failed (111: Connection refused) while connecting to upstream. Either the listener is stopped, or it is on a different port than the proxy_pass line names. Run sudo ss -tlnp and confirm something is listening on 127.0.0.1:3050 for GitHub and 127.0.0.1:3040 for Slack, then run opentag doctor for the bindings and executors.