OpenTag로 코딩 에이전트 셀프 호스팅하는 방법
Slack과 GitHub @mention을 로컬 코딩 에이전트로 연결하는 OpenTag 구축 가이드입니다. VPS 환경에서 TLS 설정, 웹훅 서명 검증, 토큰 스코프 구성 및 v0.9.0 버전 고정 배포를 위한 필수 설정을 상세히 안내합니다.
OpenTag가 에이전트를 언급할 때 수행하는 작업
OpenTag는 Slack 스레드나 GitHub 이슈에서 @mention을 사용하면 사용자가 소유한 머신에서 코딩 에이전트를 실행합니다. 누군가 이슈에 @opentag investigate this라고 댓글을 달면, 리스너가 플랫폼 이벤트를 수신하고 서명을 확인한 뒤, 해당 멘션을 바인딩된 프로젝트와 매칭합니다. 이후 로컬 체크아웃 환경에서 코딩 에이전트를 시작하고 그 결과를 동일한 스레드에 게시합니다.
이 프로젝트는 MIT 라이선스를 따르며 amplifthq/opentag에서 확인할 수 있습니다. 2026년 10월 기준으로 최신 태그된 릴리스는 2026년 8월 17일에 게시된 v0.10.0입니다. CLI는 npm 패키지로 제공됩니다. 해당 패키지에서 0.10.0은 latest 태그를, 0.11.0은 next 태그를 가지고 있으나, v0.10.0의 README는 여전히 0.9.0을 문서화된 안정 버전으로 명시하고 있으므로 이 가이드는 0.9.0 버전을 고정하여 사용합니다. 아래의 모든 명령어는 해당 버전을 고정합니다. OpenTag는 0.10.0에서 추가된 선택적 자체 호스팅 Control Plane을 위해 공식 컨테이너 이미지를 하나 게시합니다. 이 이미지는 ghcr.io/amplifthq/opentag-control-plane이며 linux/amd64용으로 빌드되었고 소스 SHA로 태그됩니다. 별도의 latest 태그가 없으므로 sha256 다이제스트로 고정해야 합니다. 이 가이드에서는 Control Plane을 사용하지 않으므로, 여기서 고정하는 대상은 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분 동안은 잘 작동합니다. 주소는 더 이상 문제가 되지 않습니다. ngrok의 무료 플랜은 계정마다 하나의 고정된 개발 도메인을 제공하며, ngrok http는 재시작할 때마다 이를 재사용하기 때문입니다. 문제는 터널 그 자체입니다. 터널은 노트북이 켜져 있고 에이전트 프로세스가 실행 중일 때만 응답하며, 노트북이 절전 모드에 들어가면 터널도 사라집니다. 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 이상을 요구하며, v0.10.0부터는 22.14 이상이 필요합니다. 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에 일반적인 포트 80 서버 블록을 작성하고 아래의 두 location을 설정한 뒤, 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 설정이 아니라고 명시되어 있습니다. 이로 인해 사용자가 간과하는 결과가 발생합니다. 봇은 해당 토큰을 생성한 사람의 계정으로 댓글을 작성합니다. 모든 분류 응답에 이름이 인용되어도 괜찮은 계정으로 토큰을 생성하십시오.
설정 가이드에 명시된 대로 범위를 최대한 좁게 지정하십시오. 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 서버를 사용하십시오. 웹 검색은 Triage에서 지속적으로 요구하는 또 다른 기능이며, 자체 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을 추적하는 대신 버전을 고정하십시오. 이 소프트웨어는 라이브 토큰을 사용하여 저장소에 대해 코딩 에이전트를 실행하므로, 밤사이에 배포된 릴리스는 검토되지 않은 변경 사항이 될 수 있습니다. 보안 정책에 따라 수정 사항은 최신 릴리스와 현재 main 브랜치를 대상으로 하며, 이전 릴리스에는 백포트가 제공되지 않을 수 있습니다. 0.9.0으로 고정된 상태는 이미 v0.10.0보다 한 버전 뒤처진 것이므로, 고정한다는 것은 변경 로그를 읽고 의도적으로 버전을 올린다는 의미입니다. v0.9.0에 영원히 머무르라는 뜻이 아닙니다. 2026년 7월까지의 기록을 보면 한 달에 여러 번 릴리스가 있었고, 8월 17일에는 v0.10.0이 뒤이어 나왔습니다. 이는 버전을 올리기 전에 릴리스 노트를 읽어야 하는 좋은 이유입니다. 또한 v0.10.0부터는 GitHub 풀 리퀘스트 실행 시 관리형 완료(governed completion)가 기본값으로 설정되며, OPENTAG_GITHUB_DEFAULT_COMPLETION=compat을 통해 이전 동작으로 되돌릴 수 있습니다.
지금부터는 릴리스 노트를 주의 깊게 읽으십시오. 2026년 10월 기준으로 main의 변경 로그 중 미출시 섹션에는 의도적인 하위 호환성 파괴(breaking change)가 포함된 1.0 이전 리셋 내용이 기술되어 있습니다. 여기에는 GitHub를 소스 앱에서 제거하고 이 가이드가 의존하는 내장 SQLite 디스패처를 삭제하는 내용이 포함됩니다. 아직 태그된 릴리스에는 적용되지 않았으나, 일단 적용되면 위에서 언급한 GitHub 웹훅 경로는 해당 업데이트 이후 더 이상 작동하지 않게 됩니다.
FAQ
OpenTag를 실행하려면 VPS가 필요한가요, 아니면 노트북으로도 충분한가요?
Slack만 사용한다면 노트북으로도 충분합니다. Socket Mode는 아웃바운드 WebSocket을 열기 때문에 인바운드 포트가 필요 없기 때문입니다. GitHub는 다릅니다. 리포지토리 웹훅은 사용자가 등록한 URL로 인바운드 HTTP 요청을 보내므로, 주소가 항상 동일해야 하며 사용자가 잠든 사이에도 응답해야 합니다. 무료 ngrok 계정을 사용하면 재시작 후에도 동일한 개발 도메인을 유지할 수 있어 주소는 고정됩니다. 하지만 터널은 노트북이 켜져 있고 에이전트가 실행 중일 때만 응답하므로 연결이 유지되지 않습니다. 에이전트가 중단되면 GitHub의 전달 요청이 실패하며, 이는 리포지토리의 Recent Deliveries 탭에 실패 항목으로 기록되고 스레드에는 아무런 반응이 없게 됩니다. 고정 DNS 이름과 인증서를 갖춘 VPS를 사용하면 이러한 문제를 해결할 수 있습니다.
OpenTag에는 어떤 GitHub 권한이 필요한가요?
Only select repositories로 제한된 세분화된(fine-grained) 개인 액세스 토큰이 필요하며, Issues: Read and write 및 Pull requests: Read and write 권한을 부여해야 합니다. 이 권한으로 멘션을 읽고 스레드에 답글을 달 수 있습니다. OpenTag가 브랜치를 푸시하도록 preparePullRequestBranch를 true로 설정하지 않는 한 코드에 대한 쓰기 권한은 필요하지 않습니다. 또한 코드 작성용 토큰과 댓글 작성용 토큰을 분리할 수 있도록 별도의 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를 실행하십시오.