OpenTag로 코딩 에이전트 셀프 호스팅하는 방법
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 패키지로 배포됩니다. 공식 컨테이너 이미지는 제공되지 않으므로 고정(pin)할 대상은 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는 ACP(agent client protocol)를 통해 실행기를 시작합니다. 이는 표준 입출력을 통해 통신하는 JSON-RPC 프로토콜이며, 에이전트는 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를 사용하는 것을 권장합니다. 설정 파일은 자격 증명을 일반 텍스트로 보관합니다. 이를 방지하려면 환경 변수나 디스크의 파일에서 값을 읽어오는 비밀 참조 방식으로 교체하십시오. 어떤 방식이든 이 파일은 서버에서 가장 민감한 정보이므로, opentag 소유로 권한을 600으로 설정하고 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에 일반 포트 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는 각 전달 내용을 웹훅 시크릿으로 서명하고 그 결과를 x-hub-signature-256 헤더에 담아 보냅니다. OpenTag는 해당 헤더를 platforms.github.webhookSecret과 대조하여 검증합니다. 프로젝트의 보안 강화 지침은 이 규칙을 명시합니다. /github/webhooks에서 서명되지 않은 소스 이벤트를 수락하지 마십시오. Slack은 각 요청에 SLACK_SIGNING_SECRET로 서명하고 타임스탬프를 포함하므로, 캡처된 본문이 몇 시간 뒤에 재전송(replay)될 수 없습니다.
이를 생략하는 것은 작은 위험이 아닙니다. 검증되지 않은 엔드포인트는 수동으로 작성된 issue_comment 페이로드를 수락하며, 여기에는 @opentag이 포함될 수 있습니다. 그러면 OpenTag는 낯선 사람의 지시에 따라 귀하의 토큰을 사용하여 귀하의 체크아웃 환경에서 코딩 에이전트를 실행합니다. 응답은 가짜 페이로드가 지정한 스레드로 전송됩니다.
OpenTag는 그 위에 두 가지 계층을 추가합니다. 소스 전달은 전달 ID로 추적되므로, 동일한 이벤트를 재전송해도 두 번째 실행이 시작되지 않습니다. 러너 호출은 멱등성 키(idempotency key)를 수락하므로, 하나를 재전송해도 추가적인 감사 이벤트를 생성하지 않고 성공을 반환합니다.
속도 제한(Rate limit)은 설정 가능하며 반드시 활성화해야 합니다. 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 권한이 포함된 앱 수준 토큰(app-level token)이 필요하며, 이는 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에 2xx 응답을 받은 issue_comment 전달 내역이 기록됩니다. 디스패처가 실행을 기록하고, 러너가 이를 할당받아 하트비트를 시작합니다. 실행기가 체크아웃을 수행하고 작업을 진행합니다. 결과는 동일한 이슈 스레드에 댓글로 달립니다. 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 서버를 활용하십시오.
에이전트가 모두가 보는 앞에서 틀린 답을 내놓으면 어떻게 됩니까?
결국 틀린 답을 내놓게 됩니다. 문제는 그로 인해 발생하는 비용입니다.
공개된 이슈에 잘못된 답변이 달리면 팀이 인식하는 계정명으로 댓글이 작성되며, GitHub는 게시되는 즉시 구독 중인 모든 사람에게 이메일을 발송합니다. 댓글을 삭제해도 이메일은 회수되지 않습니다. Slack 알림도 마찬가지입니다. 답변이 비공개 상태에서 정확할 것을 기대하기보다, 공개된 상태에서 틀릴 가능성을 염두에 두고 계획을 세워야 합니다.
피해를 제한하는 네 가지 선택지가 있으며, 이는 작성하는 어떤 프롬프트보다 중요합니다.
ask모드로 실행하여 에이전트가 제안하고 사람이 승인하게 하십시오. 잘못된 계획이라도 클릭 한 번으로 막을 수 있습니다.preparePullRequestBranch설정을 기본값인 false로 유지하십시오. 잘못된 실행의 최악의 결과가 잘못된 브랜치가 아닌 잘못된 댓글이 되도록 하기 위함입니다.- 시작 단계에서는 하나의 리포지토리와 하나의 채널만 연결하십시오. 러너(runner)는 로컬 허용 목록(allowlist) 외부의 프로젝트를 대상으로 하는 실행을 거부하므로, 연결되지 않은 리포지토리는 에이전트를 끌어들일 수 없습니다.
- 댓글 작성용 토큰과 적용(apply)용 토큰을 분리하십시오. 이렇게 하면 쓰기 권한을 취소하더라도 트리아지(triage) 기능까지 함께 중단되지 않습니다.
Slack에는 잘못된 방향으로 진행되는 실행을 중단하기 위한 /stop 명령어가 있습니다. 모든 실행은 실행을 시작하게 만든 멘션과 에이전트가 수행한 작업에 대한 감사 기록을 남깁니다. 나중에 어디서 문제가 발생했는지 파악하려면 이 기록을 읽어야 합니다.
사회적 측면도 설정만큼 중요합니다. 봇을 사람들이 기계임을 인지하고 오류가 발생할 수 있음을 알고 있는 채널에 배치하십시오. 사람이 검토했다고 가정하는 40명의 채널에서 자신 있게 틀린 답변을 내놓는 것은 트리아지로 절약한 시간보다 더 큰 비용을 치르게 합니다. 채널 설명에 봇의 소유자가 누구이며 누가 그 결과물을 확인하는지 명시하십시오.
백업, 업그레이드 및 버전 고정
모든 데이터는 /home/opentag/.config/opentag/config.json 및 /home/opentag/.local/state/opentag 두 경로에 저장됩니다. 첫 번째 경로에는 자격 증명이 포함되어 있으며, 두 번째 경로에는 실행 기록과 데이터베이스 파일이 있습니다. 두 경로 모두 mode 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
OpenTag를 실행하려면 VPS가 필요한가요, 아니면 노트북으로도 충분한가요?
Slack만 사용한다면 노트북으로도 충분합니다. Socket Mode는 아웃바운드 WebSocket을 열기 때문에 인바운드 포트가 필요 없기 때문입니다. 하지만 GitHub는 다릅니다. 리포지토리 웹훅은 한 번 등록한 URL로 인바운드 HTTP 요청을 보내므로, 주소가 항상 일정해야 하며 사용자가 잠든 사이에도 응답해야 합니다. 무료 계정의 터널 호스트는 재시작할 때마다 주소가 바뀌며, GitHub는 이전 주소로 계속 요청을 보내게 됩니다. 이 경우 리포지토리의 Recent Deliveries 탭에는 실패 기록이 남고 스레드에는 아무런 반응이 없게 됩니다. 고정된 DNS 이름과 인증서를 갖춘 VPS를 사용하면 이 두 가지 문제를 모두 해결할 수 있습니다.
OpenTag에는 어떤 GitHub 권한이 필요한가요?
Only select repositories로 제한된 세분화된(fine-grained) 개인 액세스 토큰이 필요하며, Issues: Read and write 및 Pull requests: Read and write 권한을 부여해야 합니다. 이 권한이면 멘션을 읽고 스레드에 답글을 달 수 있습니다. preparePullRequestBranch를 true로 설정하여 OpenTag가 브랜치를 푸시하도록 하지 않는 이상 코드에 대한 쓰기 권한은 필요하지 않습니다. 또한 코드 쓰기용 토큰과 댓글 작성용 토큰을 분리할 수 있도록 별도의 githubApplyToken 설정이 존재합니다. 모든 리포지토리에 대한 쓰기 권한이 포함된 토큰은 피하십시오. 해당 리포지토리에 댓글을 달 수 있는 사람이라면 누구나 커밋 권한이 있는 에이전트를 조종할 수 있기 때문입니다.
잘못 진행되고 있는 실행을 어떻게 중단하나요?
Slack에는 이를 위한 /stop 명령어가 있습니다. 서버에서는 opentag status 명령어로 실행 중인 프로세스를 확인하고, opentag service stop으로 데몬을 중단할 수 있습니다. 데몬을 중단하면 단일 실행이 아닌 전체 파이프라인이 종료됩니다. 이러한 조치가 필요 없도록 하려면 approvalMode를 ask로 설정하여 변경 사항을 적용하기 전에 사람이 직접 확인하도록 하고, preparePullRequestBranch을 false로 유지하여 잘못된 실행 시 브랜치 대신 댓글이 생성되도록 하십시오.
스레드는 조용한데 왜 웹훅에서 502 오류가 반환되나요?
502 오류는 OpenTag가 아닌 nginx에서 발생하는 것으로, 프록시가 리스너에 연결할 수 없음을 의미합니다. /var/log/nginx/error.log을 확인하면 connect() failed (111: Connection refused) while connecting to upstream을 볼 수 있습니다. 리스너가 중단되었거나, proxy_pass 라인에 지정된 포트와 실제 포트가 다를 수 있습니다. sudo ss -tlnp을 실행하여 GitHub용 127.0.0.1:3050 포트와 Slack용 127.0.0.1:3040 포트에서 무언가 수신 대기 중인지 확인하십시오. 그런 다음 바인딩과 실행기를 확인하기 위해 opentag doctor을 실행하십시오.