Multica Docs

문제 해결

Multica를 자체 호스팅할 때 흔히 겪는 문제 — 증상, 원인, 진단 방법, 해결 방법.

증상으로 문제를 찾아보세요. 각 항목은 증상 / 가능한 원인 / 진단 방법 / 해결 방법을 제공합니다. 여러분의 상황이 목록에 없다면 GitHub에 이슈를 등록하세요.

데몬이 서버에 연결되지 않음

증상: multica daemonstatus 명령이 offline 또는 connection refused를 표시합니다. 서버 로그에 /api/daemon/register/api/daemon/heartbeat 요청이 보이지 않습니다. 데몬 메커니즘이 어떻게 동작하는지는 데몬과 런타임을 참고하세요.

가능한 원인:

  1. MULTICA_SERVER_URL이 잘못된 주소를 가리킴 — 기본값은 ws://localhost:8080/ws이며, 자체 호스팅 시 여러분의 서버 주소로 변경해야 합니다
  2. 네트워크 / 방화벽 차단 — 데몬과 서버가 같은 네트워크에 있지 않거나, 아웃바운드 트래픽이 차단됨
  3. 토큰이 만료되었거나 유효하지 않음multica login을 한 번도 실행하지 않았거나, PAT이 취소됨
  4. 서버가 등록을 거부함 — 로그인한 계정이 대상 워크스페이스에 속해 있지 않음(register가 403을 반환)
  5. 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으로 표시합니다.

가능한 원인(빈도순):

  1. 에이전트 동시 실행 상한 도달 — 이 에이전트의 max_concurrent_tasks(기본값 6)가 이미 다른 실행 중인 작업들로 가득 참
  2. 같은 이슈에서 같은 에이전트의 다른 작업이 아직 실행 중 — 같은 에이전트 × 같은 이슈는 순차 실행이 강제됩니다(중복 실행 방지)
  3. 에이전트가 보관됨 — 보관 후에도 새 작업은 여전히 대기열에 들어가지만 클레임될 수 없으며, 5분 뒤 타임아웃됩니다(code-issue G-01)
  4. 데몬이 현재 워크스페이스에 이 런타임을 등록하지 않음 — 데몬을 재시작하거나 UI에서 런타임을 다시 선택하세요
  5. 데몬 연결 끊김 — 최근 45초 동안 하트비트가 없었습니다. daemon statusonline으로 보이는 것은 방금 끊긴 상태를 반영한 것일 수 있습니다

진단 방법:

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가 기록됩니다. 페이지에 실시간 업데이트(작업 진행 상황, 댓글, 인박스)가 표시되지 않아 새로고침해야 보입니다. 백엔드 작업은 여전히 실행됩니다.

가능한 원인:

  1. Origin 검사 실패 — 여러분의 프런트엔드 도메인이 서버의 CORS 허용 목록에 없습니다. 기본 허용 목록에는 localhost:3000/5173/5174만 포함되며, 공개 인터넷에서 자체 호스팅하려면 FRONTEND_ORIGIN이 필요합니다
  2. 프로토콜 불일치https:// 프런트엔드에는 wss://가 필요하고, HTTP는 ws://를 사용합니다
  3. 리버스 프록시가 WebSocket 업그레이드를 활성화하지 않음 — Nginx / Envoy / HAProxy는 기본적으로 Upgrade 헤더를 전달하지 않습니다
  4. JWT 쿠키 만료 또는 누락 — 30일 만료 후 다시 로그인하지 않음

진단 방법:

  • 브라우저 DevTools → Network → "WS"로 필터링하여 연결 상태와 상태 코드를 확인하세요
  • 서버 로그에서 "rejected origin" / "websocket"을 grep하세요 — origin 문제라면 명시적으로 표기됩니다
  • curl -i http://<server-host>:8080/ws101 Switching Protocols를 반환해야 합니다(Upgrade 헤더 포함)

해결 방법:

  • Origin 오류 → 서버의 .envFRONTEND_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:"

기대했던 줄이 보이지 않는다면 환경 변수가 프로세스에 도달하지 않은 것입니다 — .envdocker compose -f docker-compose.selfhost.yml exec backend env | grep -E 'RESEND_|SMTP_'를 확인하세요. 이 시작 로그 줄에는 자격 증명이 절대 기록되지 않습니다.

Resend가 활성 제공자일 때

가능한 원인:

  1. RESEND_API_KEY가 설정되지 않음 — 서버는 조용히 폴백하여 코드를 자체 stdout에 기록하며 오류를 내지 않습니다. 프로덕션에서 쉽게 빠질 수 있는 함정입니다
  2. Resend API 키가 유효하지 않거나 할당량 소진 — 서버 로그에 "failed to send verification code"가 표시됩니다
  3. RESEND_FROM_EMAIL의 도메인이 Resend에서 검증되지 않음 — Resend가 발송을 거부합니다
  4. 이메일은 발송되었으나 수신자 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 senderrelay가 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 relayrelay의 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_USERNAMESMTP_PASSWORD를 비워 두세요
  • Unable to relay → 내부 수신자로 제한하거나, Exchange receive connector에서 Multica 호스트의 IP에 중계 권한을 부여하세요

고정 로컬 테스트 코드가 동작하지 않음

증상: 자체 호스팅 인스턴스에서 888888 같은 고정 로컬 테스트 코드로 로그인하려 했지만 invalid or expired code로 거부됩니다.

가능한 원인(상호 배타적):

  1. MULTICA_DEV_VERIFICATION_CODE가 비어 있음 — 고정 코드는 기본적으로 비활성화되어 있습니다
  2. APP_ENV=production — 이것은 올바른 프로덕션 구성입니다. 고정 로컬 테스트 코드는 프로덕션에서 무시됩니다
  3. 구성된 코드가 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=developmentMULTICA_DEV_VERIFICATION_CODE=888888을 설정하세요 — 공개 인스턴스에서는 고정 코드를 절대 활성화하지 마세요(자세한 내용은 로그인 및 회원가입 구성 → 고정 로컬 테스트 코드 참고)

사용량 대시보드가 0으로 유지됨

증상: 에이전트가 작업을 완료하고 원시 토큰 사용량이 데이터베이스에 기록되지만, 설정 → 사용량설정 → 런타임에서 입력 / 출력 / 비용이 전부 0으로 표시됩니다. 이것은 조용히 발생하는 현상으로 — 백엔드 로그에 오류가 없습니다.

가능한 원인:

  1. rollup_task_usage_hourly()가 전혀 스케줄링되지 않음 — 사용량 / 런타임 대시보드는 파생 테이블 task_usage_hourly에서 읽으며, 이 테이블은 해당 함수로 채워집니다. 번들된 pgvector/pgvector:pg17 이미지에는 pg_cron이 포함되어 있지 않으며, 백엔드도 프로세스 내에서 rollup을 실행하지 않습니다. 외부 스케줄러 없이 새로 설치한 자체 호스팅에서는 이것이 기본 상태입니다.
  2. pg_cron이 설치되었지만 잘못된 데이터베이스를 가리킴pg_cron.database_name의 기본값은 postgres입니다. Multica 데이터베이스 이름이 다르면 스케줄된 작업이 rollup_task_usage_hourly()를 전혀 보지 못합니다.
  3. 스케줄러는 실행되지만 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를 실행하여 워터마크 이전의 버킷을 채우세요.

마이그레이션 103refusing 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되지 않았을 때 — 이 가드가 발동합니다.

해결 방법:

  1. 같은 데이터베이스에 대해 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
  2. 업그레이드를 다시 실행하세요 — 백엔드 컨테이너를 재시작하는 것으로 충분하며, 마이그레이션은 시작 시 실행됩니다. 이제 가드가 최신 워터마크를 확인하고 103을 적용하도록 허용합니다.

  3. 워터마크가 계속 진행되도록 지속적인 rollup 스케줄(cron / pg_cron)을 설정하세요 — 자체 호스팅 빠른 시작 → 사용량 rollup 스케줄링을 참고하세요.

--sleep-between-slices=2s는 수년 치 이력이 있는 프로덕션 데이터베이스에서 적절한 기본값입니다. 최근 N개월만 보관하고 더 오래된 버킷을 영구히 포기해도 괜찮다면 --months-back N --force-partial을 사용하세요.

포트 충돌

증상: multica servermultica daemon startaddress already in use로 실패합니다.

가능한 원인:

  1. 서버 포트 점유됨(기본값 8080)
  2. 데몬 health 포트 점유됨(기본값 19514, 프로필마다 해시로 오프셋됨)
  3. Web dev 서버 포트 충돌(3000 / 5173)
  4. 포트에 대한 권한 부족(< 1024 특권 포트 바인딩에는 sudo 필요)

진단 방법:

lsof -i :8080        # macOS / Linux
netstat -ano | findstr :8080    # Windows

해결 방법:

  • 충돌하는 프로세스를 종료하거나(kill -9 <PID>), PORT=9000으로 포트를 변경하세요
  • 80 / 443을 사용하려면 → 직접 바인딩하지 말고, 앞에 리버스 프록시(Nginx / Caddy)를 두어 높은 포트로 전달하세요

로그를 찾는 위치

구성 요소위치명령
데몬~/.multica/daemon.log(백그라운드 모드) 또는 포그라운드 stdoutmultica daemon logs -f --lines 100
서버(Docker)컨테이너 stdoutdocker logs -f <container>
서버(systemd)journaljournalctl -u multica-server -f
프런트엔드(dev)pnpm dev를 실행 중인 터미널직접 확인
프런트엔드(브라우저)DevTools → ConsoleF12를 누르세요

더 자세한 데몬 로그가 필요하면, 데몬을 백그라운드에서 포그라운드로 옮기세요: multica daemon stop && multica daemon start --foreground.