문제 해결
Multica를 자체 호스팅할 때 흔히 겪는 문제 — 증상, 원인, 진단 방법, 해결 방법.
증상으로 문제를 찾아보세요. 각 항목은 증상 / 가능한 원인 / 진단 방법 / 해결 방법을 제공합니다. 여러분의 상황이 목록에 없다면 GitHub에 이슈를 등록하세요.
데몬이 서버에 연결되지 않음
증상: multica daemon의 status 명령이 offline 또는 connection refused를 표시합니다. 서버 로그에 /api/daemon/register나 /api/daemon/heartbeat 요청이 보이지 않습니다. 데몬 메커니즘이 어떻게 동작하는지는 데몬과 런타임을 참고하세요.
가능한 원인:
MULTICA_SERVER_URL이 잘못된 주소를 가리킴 — 기본값은ws://localhost:8080/ws이며, 자체 호스팅 시 여러분의 서버 주소로 변경해야 합니다- 네트워크 / 방화벽 차단 — 데몬과 서버가 같은 네트워크에 있지 않거나, 아웃바운드 트래픽이 차단됨
- 토큰이 만료되었거나 유효하지 않음 —
multica login을 한 번도 실행하지 않았거나, PAT이 취소됨 - 서버가 등록을 거부함 — 로그인한 계정이 대상 워크스페이스에 속해 있지 않음(register가 403을 반환)
- DNS 해석 실패 — 데몬 기기에서 호스트명이 해석되지 않음
진단 방법:
multica daemon logs --lines 100 # look for daemon-side errors
echo $MULTICA_SERVER_URL # confirm the address is set
curl -i http://<server-host>:8080/health # hit the server directly
curl -i http://<server-host>:8080/readyz # include DB + migration readiness
cat ~/.multica/config.json # verify api_token exists
multica workspace list # confirm you're a member of the target workspace해결 방법: 위의 각 원인을 하나씩 처리하세요. 가장 흔한 두 가지 해결책은 MULTICA_SERVER_URL을 변경하고 데몬을 재시작하는 것(multica daemon restart)과 다시 로그인하는 것(multica logout && multica login)입니다.
작업이 queued에서 멈춤
증상: 에이전트에게 이슈를 할당한 뒤 이슈 상태가 곧바로 in_progress로 바뀌지만, 오랜 시간이 지나도 페이지에 에이전트 실행의 흔적이 보이지 않습니다. multica daemon status는 데몬을 online으로 표시합니다.
가능한 원인(빈도순):
- 에이전트 동시 실행 상한 도달 — 이 에이전트의
max_concurrent_tasks(기본값 6)가 이미 다른 실행 중인 작업들로 가득 참 - 같은 이슈에서 같은 에이전트의 다른 작업이 아직 실행 중 — 같은 에이전트 × 같은 이슈는 순차 실행이 강제됩니다(중복 실행 방지)
- 에이전트가 보관됨 — 보관 후에도 새 작업은 여전히 대기열에 들어가지만 클레임될 수 없으며, 5분 뒤 타임아웃됩니다(code-issue G-01)
- 데몬이 현재 워크스페이스에 이 런타임을 등록하지 않음 — 데몬을 재시작하거나 UI에서 런타임을 다시 선택하세요
- 데몬 연결 끊김 — 최근 45초 동안 하트비트가 없었습니다.
daemon status가online으로 보이는 것은 방금 끊긴 상태를 반영한 것일 수 있습니다
진단 방법:
multica daemon status --output json # runtime list + last_seen_at
multica agent list # check agent archived state
multica issue show <issue-id> # inspect task history서버 측(자체 호스팅)에서는 "no_tasks" / "no_capacity"를 grep하여 클레임 결과를 확인할 수 있습니다.
해결 방법:
- 동시 실행 가득 참 → 실행 중인 작업이 끝나기를 기다리거나,
multica agent update <id> --max-concurrent-tasks 10으로 상한을 올리세요 - 같은 이슈 순차 실행 → 이전 작업이 끝나기를 기다리거나, 다른 에이전트에게 다시 할당하세요
- 에이전트 보관됨 →
multica agent restore <id> - 런타임 미등록 →
multica daemon restart하면 데몬이 다시 등록합니다
WebSocket이 연결되지 않음
증상: 브라우저 콘솔에 WebSocket is closed가 기록됩니다. 페이지에 실시간 업데이트(작업 진행 상황, 댓글, 인박스)가 표시되지 않아 새로고침해야 보입니다. 백엔드 작업은 여전히 실행됩니다.
가능한 원인:
- Origin 검사 실패 — 여러분의 프런트엔드 도메인이 서버의 CORS 허용 목록에 없습니다. 기본 허용 목록에는
localhost:3000/5173/5174만 포함되며, 공개 인터넷에서 자체 호스팅하려면FRONTEND_ORIGIN이 필요합니다 - 프로토콜 불일치 —
https://프런트엔드에는wss://가 필요하고, HTTP는ws://를 사용합니다 - 리버스 프록시가 WebSocket 업그레이드를 활성화하지 않음 — Nginx / Envoy / HAProxy는 기본적으로
Upgrade헤더를 전달하지 않습니다 - JWT 쿠키 만료 또는 누락 — 30일 만료 후 다시 로그인하지 않음
진단 방법:
- 브라우저 DevTools → Network → "WS"로 필터링하여 연결 상태와 상태 코드를 확인하세요
- 서버 로그에서
"rejected origin"/"websocket"을 grep하세요 — origin 문제라면 명시적으로 표기됩니다 curl -i http://<server-host>:8080/ws는101 Switching Protocols를 반환해야 합니다(Upgrade헤더 포함)
해결 방법:
- Origin 오류 → 서버의
.env에FRONTEND_ORIGIN=https://multica.yourdomain.com을 설정(또는 쉼표로 구분한CORS_ALLOWED_ORIGINS)하고 서버를 재시작하세요 - 프로토콜 불일치 →
FRONTEND_ORIGIN의 프로토콜이 프런트엔드와 일치하는지 확인하세요 - 리버스 프록시 → Nginx에
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";를 추가하세요 - 쿠키 만료 → 페이지를 새로고침하고 다시 로그인하세요
이메일이 수신되지 않음
증상: 로그인 또는 초대 수락 중 이메일을 제출했는데, 인박스에도 스팸 폴더에도 인증 코드가 없습니다.
먼저 서버가 어떤 제공자를 활성 상태로 인식하는지 확인하세요. 시작 시 백엔드는 다음 중 하나를 출력합니다:
EmailService: SMTP relay <host>:<port> from=<addr>— SMTP 사용(SMTP_HOST가 비어 있지 않으면 Resend보다 우선)EmailService: Resend API from=<addr>— Resend 사용EmailService: DEV mode — codes printed to stdout …— 제공자가 구성되지 않음
docker compose -f docker-compose.selfhost.yml logs backend | grep "EmailService:"기대했던 줄이 보이지 않는다면 환경 변수가 프로세스에 도달하지 않은 것입니다 — .env와 docker compose -f docker-compose.selfhost.yml exec backend env | grep -E 'RESEND_|SMTP_'를 확인하세요. 이 시작 로그 줄에는 자격 증명이 절대 기록되지 않습니다.
Resend가 활성 제공자일 때
가능한 원인:
RESEND_API_KEY가 설정되지 않음 — 서버는 조용히 폴백하여 코드를 자체 stdout에 기록하며 오류를 내지 않습니다. 프로덕션에서 쉽게 빠질 수 있는 함정입니다- Resend API 키가 유효하지 않거나 할당량 소진 — 서버 로그에
"failed to send verification code"가 표시됩니다 RESEND_FROM_EMAIL의 도메인이 Resend에서 검증되지 않음 — Resend가 발송을 거부합니다- 이메일은 발송되었으나 수신자 ISP가 스팸으로 표시 — Resend 대시보드와 스팸 폴더를 확인하세요
진단 방법:
- 서버 로그에서
"[DEV] Verification code for"를 grep하세요 — 있다면 Resend가 구성되지 않았고 코드가 stdout에 기록된 것입니다 - Resend 대시보드 → Emails에서 발송 이력을 확인하세요
RESEND_FROM_EMAIL의 도메인이 Resend 콘솔의 "Verified Domains" 목록에 나타나는지 확인하세요
해결 방법:
- API 키 누락 → 로그인 및 회원가입 구성 → 이메일 동작 방식을 따라 구성한 뒤 서버를 재시작하세요
- 도메인 미검증 → Resend 콘솔에서 DNS 검증 절차를 진행하세요(SPF / DKIM 레코드 추가)
- 긴급 상황(내부 테스트) → 서버 로그에서
[DEV]아래에 출력된 코드를 복사하세요
SMTP가 활성 제공자일 때
SMTP 경로는 모든 실패를 실패한 단계와 함께 감싸므로, 서버 로그가 이미 relay가 어느 단계에서 세션을 거부했는지 알려줍니다. "failed to send verification email" / "failed to send invitation email"을 grep하고 감싸진 오류를 확인하세요:
| 기록된 오류 | 의미 | 해결 방법 |
|---|---|---|
smtp dial <host>:<port>: dial tcp …: connect: connection refused / i/o timeout | 백엔드 컨테이너가 relay에 도달할 수 없음 — 잘못된 host, 잘못된 port, 방화벽, 또는 relay가 수신 대기 중이 아님 | 컨테이너 내부에서 SMTP_HOST / SMTP_PORT가 해석되는지 확인하세요(docker compose -f docker-compose.selfhost.yml exec backend nslookup <host> 및 nc -vz <host> <port>). Multica를 실행하는 호스트에서 relay로 향하는 방화벽을 여세요 |
smtp starttls: x509: certificate signed by unknown authority(또는 certificate is not valid for any names) | relay가 사설 CA / 자체 서명 인증서를 사용하며, 컨테이너의 신뢰 저장소가 이를 거부함 | CA를 컨테이너에 설치하거나, relay가 신뢰할 수 있는 네트워크 구간에서 도달 가능함을 확인한 후에만 SMTP_TLS_INSECURE=true를 설정하세요 |
smtp auth: 535 5.7.8 Authentication credentials invalid(또는 534/530) | SMTP_USERNAME / SMTP_PASSWORD가 잘못되었거나, relay가 PLAIN이 아닌 다른 인증 방식을 요구함 | 메일 관리자에게 서비스 계정 자격 증명을 다시 확인하세요. Exchange 익명 내부 relay의 경우 둘 다 비워 두세요(SMTP_USERNAME=, SMTP_PASSWORD=) |
smtp MAIL FROM: 550 5.7.1 Client does not have permissions to send as this sender | relay가 RESEND_FROM_EMAIL을 봉투 발신자로 수락하지 않음 — 전형적인 Exchange "anonymous users not allowed" 또는 DMARC 정렬 문제 | RESEND_FROM_EMAIL을 relay가 수락하는 도메인으로 설정하세요. Exchange에서는 receive connector에서 원본 IP에 ms-Exch-SMTP-Accept-Any-Sender를 부여하세요 |
smtp RCPT TO <addr>: 550 5.7.1 Unable to relay | relay의 receive connector가 여러분의 서브넷이 외부 수신자에게 중계하는 것을 허용하지 않음(외부 도메인과 통신하는 익명 내부 relay에서 가장 흔함) | 초대를 내부 수신자로 제한하거나, Multica 호스트의 서브넷을 Exchange "Anonymous Users → Relay" 권한 목록에 추가하세요 |
smtp DATA / smtp write body / smtp end data | 세션은 수락되었으나 relay가 본문을 폐기함 — 보통 메시지 크기 제한, 콘텐츠 필터링, 또는 전송 중 연결 재설정 때문 | relay 로그에서 동일한 Message-ID(로그에는 <unixnano>@<host> 형식)를 확인하세요. 필요하면 메시지 크기 제한을 높이세요 |
MAIL FROM, RCPT TO, DATA 오류는 항상 relay의 응답 코드와 함께 기록되므로 반대편의 Exchange / Postfix 로그와 대조할 수 있습니다. 인증 코드와 초대 토큰은 감싸진 오류에 절대 포함되지 않습니다.
진단 방법:
- 시작 시
"EmailService: SMTP relay"를 한 번 grep하고, 런타임 실패에 대해서는"failed to send"를 grep하세요 - 백엔드 컨테이너 내부에서 연결성을 점검하세요:
docker compose -f docker-compose.selfhost.yml exec backend sh -c 'nc -vz $SMTP_HOST $SMTP_PORT' - 환경 변수가 프로세스에 도달했는지 확인하세요:
docker compose -f docker-compose.selfhost.yml exec backend env | grep SMTP_(출력에 비밀번호가 포함되므로 신뢰할 수 있는 셸에서만 실행하세요)
해결 방법:
- 잘못된 host / port →
SMTP_HOST/SMTP_PORT를 조정하고 백엔드를 재시작하세요. 지원되는 relay 모드는 인증 설정 → Option B: SMTP relay를 참고하세요 - 인증서 불일치 → relay의 CA를 컨테이너에 설치하거나, 신뢰할 수 있는 네트워크 구간에서 임시로
SMTP_TLS_INSECURE=true를 설정하세요 - 인증 실패 → 자격 증명을 다시 확인하세요. 익명 내부 relay의 경우
SMTP_USERNAME과SMTP_PASSWORD를 비워 두세요 Unable to relay→ 내부 수신자로 제한하거나, Exchange receive connector에서 Multica 호스트의 IP에 중계 권한을 부여하세요
고정 로컬 테스트 코드가 동작하지 않음
증상: 자체 호스팅 인스턴스에서 888888 같은 고정 로컬 테스트 코드로 로그인하려 했지만 invalid or expired code로 거부됩니다.
가능한 원인(상호 배타적):
MULTICA_DEV_VERIFICATION_CODE가 비어 있음 — 고정 코드는 기본적으로 비활성화되어 있습니다APP_ENV=production— 이것은 올바른 프로덕션 구성입니다. 고정 로컬 테스트 코드는 프로덕션에서 무시됩니다- 구성된 코드가 6자리가 아님 — 이 단축 코드는 6자리 값만 허용합니다
진단 방법:
cat .env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE'
docker exec <container> env | grep -E 'APP_ENV|MULTICA_DEV_VERIFICATION_CODE'인박스(스팸 포함)에서 실제 인증 코드를 확인하세요.
해결 방법:
- 프로덕션에서는
MULTICA_DEV_VERIFICATION_CODE를 비워 두고, Resend를 구성하여 실제 코드를 사용하세요 - 로컬 개발이나 내부 테스트의 경우 서버 로그에서 생성된 코드를 복사하거나,
APP_ENV=development와MULTICA_DEV_VERIFICATION_CODE=888888을 설정하세요 — 공개 인스턴스에서는 고정 코드를 절대 활성화하지 마세요(자세한 내용은 로그인 및 회원가입 구성 → 고정 로컬 테스트 코드 참고)
사용량 대시보드가 0으로 유지됨
증상: 에이전트가 작업을 완료하고 원시 토큰 사용량이 데이터베이스에 기록되지만, 설정 → 사용량과 설정 → 런타임에서 입력 / 출력 / 비용이 전부 0으로 표시됩니다. 이것은 조용히 발생하는 현상으로 — 백엔드 로그에 오류가 없습니다.
가능한 원인:
rollup_task_usage_hourly()가 전혀 스케줄링되지 않음 — 사용량 / 런타임 대시보드는 파생 테이블task_usage_hourly에서 읽으며, 이 테이블은 해당 함수로 채워집니다. 번들된pgvector/pgvector:pg17이미지에는pg_cron이 포함되어 있지 않으며, 백엔드도 프로세스 내에서 rollup을 실행하지 않습니다. 외부 스케줄러 없이 새로 설치한 자체 호스팅에서는 이것이 기본 상태입니다.pg_cron이 설치되었지만 잘못된 데이터베이스를 가리킴 —pg_cron.database_name의 기본값은postgres입니다. Multica 데이터베이스 이름이 다르면 스케줄된 작업이rollup_task_usage_hourly()를 전혀 보지 못합니다.- 스케줄러는 실행되지만 rollup이 조용히 오류를 냄 — 예를 들어 cron 항목 내부의 DB 역할 / search_path가 잘못됨.
진단 방법:
-- Confirm raw events exist but the hourly table is empty.
SELECT count(*) AS raw_rows FROM task_usage;
SELECT count(*) AS hourly_rows FROM task_usage_hourly;
-- Confirm pg_cron is (or isn't) available.
SELECT * FROM pg_available_extensions WHERE name = 'pg_cron';
SHOW shared_preload_libraries;
-- If pg_cron is installed, check the schedule + last run.
SELECT jobname, schedule, database, active FROM cron.job;
SELECT jobname, status, return_message, start_time, end_time
FROM cron.job_run_details ORDER BY start_time DESC LIMIT 10;
-- Watermark — if this is 1970-01-01, the rollup has never run.
SELECT watermark_at FROM task_usage_hourly_rollup_state;해결 방법:
- rollup을 수동으로 한 번 호출하여 동작하는지 확인하세요:
SELECT rollup_task_usage_hourly();— 대시보드를 새로고침하세요. 숫자가 나타나면 빠진 것은 스케줄러뿐입니다. - 자체 호스팅 빠른 시작 → 사용량 rollup 스케줄링에서 지원되는 방식 중 하나를 선택하세요: 외부 cron / systemd-timer / Kubernetes CronJob, 또는 Postgres를
pg_cron이 포함된 이미지로 교체. - 스케줄 설정 이전의 이력이 이미 있다면, 백엔드 컨테이너 내부에서
backfill_task_usage_hourly를 실행하여 워터마크 이전의 버킷을 채우세요.
마이그레이션 103이 refusing to drop legacy daily rollups로 실패함
증상: v0.3.4에서 v0.3.5+로 업그레이드할 때 백엔드 컨테이너가 시작되지 않거나(또는 migrate up이 중단됨) 다음과 같은 오류가 발생합니다:
ERROR: refusing to drop legacy daily rollups:
task_usage_hourly_rollup_state.watermark_at (1970-01-01 ...) trails
task_usage latest event (...) by more than 01:00:00 — backfill is
incomplete or pg_cron is not running. Run cmd/backfill_task_usage_hourly
(and let pg_cron catch up) before re-running migrate가능한 원인: 이것은 마이그레이션 103의 fail-closed 가드입니다. task_usage_hourly가 원시 task_usage를 따라잡을 때까지 레거시 daily rollup 삭제를 거부합니다. 기존 행이 존재하고 rollup 워터마크가 여전히 epoch에 머물러 있을 때 — 즉 아직 어떤 이력도 hourly 테이블로 rollup되지 않았을 때 — 이 가드가 발동합니다.
해결 방법:
-
같은 데이터베이스에 대해 backfill을 실행하세요(멱등하며, 중단해도 안전하고, 다시 실행해도 안전합니다):
# Docker Compose docker compose -f docker-compose.selfhost.yml exec backend \ ./backfill_task_usage_hourly --sleep-between-slices=2s # Kubernetes kubectl -n multica exec deploy/multica-backend -- \ ./backfill_task_usage_hourly --sleep-between-slices=2s -
업그레이드를 다시 실행하세요 — 백엔드 컨테이너를 재시작하는 것으로 충분하며, 마이그레이션은 시작 시 실행됩니다. 이제 가드가 최신 워터마크를 확인하고
103을 적용하도록 허용합니다. -
워터마크가 계속 진행되도록 지속적인 rollup 스케줄(cron /
pg_cron)을 설정하세요 — 자체 호스팅 빠른 시작 → 사용량 rollup 스케줄링을 참고하세요.
--sleep-between-slices=2s는 수년 치 이력이 있는 프로덕션 데이터베이스에서 적절한 기본값입니다. 최근 N개월만 보관하고 더 오래된 버킷을 영구히 포기해도 괜찮다면 --months-back N --force-partial을 사용하세요.
포트 충돌
증상: multica server나 multica daemon start가 address already in use로 실패합니다.
가능한 원인:
- 서버 포트 점유됨(기본값
8080) - 데몬 health 포트 점유됨(기본값
19514, 프로필마다 해시로 오프셋됨) - Web dev 서버 포트 충돌(
3000/5173) - 포트에 대한 권한 부족(
< 1024특권 포트 바인딩에는 sudo 필요)
진단 방법:
lsof -i :8080 # macOS / Linux
netstat -ano | findstr :8080 # Windows해결 방법:
- 충돌하는 프로세스를 종료하거나(
kill -9 <PID>),PORT=9000으로 포트를 변경하세요 - 80 / 443을 사용하려면 → 직접 바인딩하지 말고, 앞에 리버스 프록시(Nginx / Caddy)를 두어 높은 포트로 전달하세요
로그를 찾는 위치
| 구성 요소 | 위치 | 명령 |
|---|---|---|
| 데몬 | ~/.multica/daemon.log(백그라운드 모드) 또는 포그라운드 stdout | multica daemon logs -f --lines 100 |
| 서버(Docker) | 컨테이너 stdout | docker logs -f <container> |
| 서버(systemd) | journal | journalctl -u multica-server -f |
| 프런트엔드(dev) | pnpm dev를 실행 중인 터미널 | 직접 확인 |
| 프런트엔드(브라우저) | DevTools → Console | F12를 누르세요 |
더 자세한 데몬 로그가 필요하면, 데몬을 백그라운드에서 포그라운드로 옮기세요: multica daemon stop && multica daemon start --foreground.