Multica Docs

故障排查

self-host Multica 常见问题——症状、原因、怎么查、怎么修。

按症状查问题。每条问题都给症状 / 可能原因 / 怎么查 / 怎么修四段。如果你的情况不在下面,到 GitHub 提 issue。

守护进程连不上服务器

症状multica daemonstatus 命令显示 offlineconnection refused;服务器日志里没有 /api/daemon/register/api/daemon/heartbeat 的请求。守护进程机制详见 守护进程与运行时

可能原因

  1. MULTICA_SERVER_URL 指错地址 —— 默认是 ws://localhost:8080/ws,self-host 要改成你自己的 server 地址
  2. 网络 / 防火墙阻挡 —— daemon 和 server 不在同一网络,或出站被 block
  3. Token 过期或无效 —— 你从来没跑过 multica login,或 PAT 被撤销
  4. 服务器拒绝注册 —— 你登录的账号不在目标工作区(register 返 403)
  5. DNS 解析失败 —— hostname 在 daemon 机器上解不出来

怎么查

multica daemon logs --lines 100    # 看 daemon 侧错误
echo $MULTICA_SERVER_URL          # 确认地址配对
curl -i http://<server-host>:8080/health   # 直接戳 server
curl -i http://<server-host>:8080/readyz  # 连同 DB + migration readiness 一起检查
cat ~/.multica/config.json        # 看 api_token 是否存在
multica workspace list            # 确认你是目标工作区成员

怎么修:按上面原因对症处理。最常见的两个是MULTICA_SERVER_URL 重启 daemonmultica daemon restart)和重新登录multica logout && multica login)。

任务一直卡在 queued

症状:把 issue 分给 agent 后,issue 状态立刻变 in_progress,但过了很久页面没有 agent 执行的迹象;multica daemon status 显示 daemon online

可能原因(按触发概率排):

  1. 智能体并发上限已满 —— 该 agent 的 max_concurrent_tasks(默认 6)已经被其他正在跑的任务占满
  2. 同一 issue 上有另一个同 agent 的任务还没结束 —— 同 agent × 同 issue 强制串行(防止重复执行)
  3. 智能体已经被 archive —— 被归档后新任务仍能入队,但无法被 claim,会卡到 5 分钟超时(code-issue G-01)
  4. Daemon 没在当前工作区注册该 runtime —— 重启 daemon 或在 UI 重新选一次 runtime
  5. 守护进程失联 —— 最近 45 秒没心跳。daemon status 看起来 online 也可能是刚失联

怎么查

multica daemon status --output json       # runtime 列表 + last_seen_at
multica agent list                         # 查 agent 的 archived 状态
multica issue show <issue-id>             # 看 task 历史

服务器侧(self-host)可以 grep "no_tasks" / "no_capacity" 看 claim 的结果。

怎么修

  • 并发打满 → 等现有任务跑完,或 multica agent update <id> --max-concurrent-tasks 10 提升上限
  • 同 issue 串行 → 等前一个任务结束,或改分给不同 agent
  • Agent 被 archive → multica agent restore <id>
  • Runtime 未注册 → multica daemon restart,daemon 会重新注册

WebSocket 连不上

症状:浏览器控制台报 WebSocket is closed;页面不显示实时更新(任务进度、评论、inbox),刷新才能看到;但后台任务仍在执行。

可能原因

  1. Origin 校验失败 —— 你的前端域名不在 server 的 CORS 白名单里。默认白名单只包含 localhost:3000/5173/5174,self-host 到公网必须配 FRONTEND_ORIGIN
  2. 协议不匹配 —— 前端用 https:// 需要 wss://,HTTP 用 ws://
  3. 反向代理没开 WebSocket upgrade —— Nginx / Envoy / HAProxy 默认不转发 Upgrade header
  4. JWT cookie 过期或丢失 —— 30 天过期后没重登

怎么查

  • 浏览器 DevTools → Network → 筛选 "WS",看连接状态和状态码
  • Server 日志里 grep "rejected origin" / "websocket" —— 如果是 origin 问题会明确写出来
  • curl -i http://<server-host>:8080/ws 应该返回 101 Switching Protocols(需要带 Upgrade header)

怎么修

  • Origin 错 → 在 server 的 .envFRONTEND_ORIGIN=https://multica.yourdomain.com(或逗号分隔的 CORS_ALLOWED_ORIGINS),重启 server
  • 协议不匹配 → 确保 FRONTEND_ORIGIN 的协议和前端一致
  • 反向代理 → 在 Nginx 加 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
  • Cookie 过期 → 刷新页面重新登录

邮件没收到

症状:登录或邀请时提交邮箱后,收件箱(和垃圾邮件)里都没有验证码邮件。

先确认 server 自己认为在用哪个 provider。 启动时 backend 会打印这三种之一:

  • EmailService: SMTP relay <host>:<port> from=<addr> —— 走 SMTP(SMTP_HOST 非空时优先级高于 Resend)
  • EmailService: Resend API from=<addr> —— 走 Resend
  • EmailService: DEV mode — codes printed to stdout … —— 没配任何 provider
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 是当前 provider

可能原因

  1. RESEND_API_KEY 没配 —— server 会静默回落,把验证码打到自己的 stdout 里,不报错。生产部署很容易踩
  2. Resend API key 无效 / 余额不足 —— server 日志会有 "failed to send verification code"
  3. RESEND_FROM_EMAIL 的域名没在 Resend 验证 —— Resend 会拒发
  4. 邮件发出去了但被收件人 ISP 判垃圾 —— 查 Resend dashboard 和 spam 目录

怎么查

  • Server 日志里搜 "[DEV] Verification code for" —— 如果有,说明 Resend 没配,验证码被打到 stdout
  • Resend dashboard → Emails 看发送记录
  • 确认 RESEND_FROM_EMAIL 的域名在 Resend console 的 "Verified Domains" 列表里

怎么修

  • 没配 API key → 照 登录与注册配置 → 怎么配 Email 的步骤配完重启 server
  • 域名没验证 → Resend console 里走 DNS 验证流程(加 SPF / DKIM 记录)
  • 紧急情况下(如内部测试)→ 从 server 日志里抄 [DEV] 打印出的验证码

SMTP 是当前 provider

SMTP 路径把每个失败都按阶段包装好,所以 server 日志已经告诉你 relay 在哪一步拒绝了会话。搜 "failed to send verification email" / "failed to send invitation email",看里面包的具体错误:

错误日志含义怎么修
smtp dial <host>:<port>: dial tcp …: connect: connection refused / i/o timeoutbackend 容器连不上 relay —— host / port 错、防火墙挡了、或者 relay 没开在容器里确认能解析和连通: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 namesrelay 用了私有 CA / 自签证书,容器的信任库不接受要么把 CA 装进容器,要么在确认 relay 走的是可信网段后设 SMTP_TLS_INSECURE=true
smtp auth: 535 5.7.8 Authentication credentials invalid(或 534/530SMTP_USERNAME / SMTP_PASSWORD 不对,或 relay 不接受 PLAIN 认证找邮件管理员复核 service account 凭据;Exchange 匿名内部 relay 应当把两者都留空
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 上给来源 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会话被接受但 body 被丢 —— 通常是消息大小限制、内容过滤、或中途断连在 relay 端按同一个 Message-ID(日志里是 <unixnano>@<host>)找上下文;必要时调大消息大小限制

MAIL FROM / RCPT TO / DATA 的错误日志里都带着 relay 返回的状态码,可以和 Exchange / Postfix 那边的日志对齐。验证码和邀请 token 不会出现在这些包装的错误里。

怎么查

  • 启动时搜 "EmailService: SMTP relay" 一次,运行时搜 "failed to send" 看具体阶段
  • 在 backend 容器里测连通: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 后重启 backend;支持的 relay 模式见 登录与注册配置 → Option B:SMTP relay
  • 证书校验失败 → 把 relay 的 CA 装进容器,或在可信网段上临时 SMTP_TLS_INSECURE=true
  • 认证失败 → 复核凭据;匿名内部 relay 应把 SMTP_USERNAMESMTP_PASSWORD 都留空
  • Unable to relay → 邀请仅限内部域,或在 Exchange receive connector 上给 Multica 主机授中继权限

固定本地测试验证码登不进去

症状:自部署实例,想用 888888 这类固定本地测试验证码登录,但被拒 invalid or expired code

可能原因(互斥):

  1. MULTICA_DEV_VERIFICATION_CODE 为空 —— 固定验证码默认关闭
  2. APP_ENV=production —— 这是正确的生产配置;固定本地测试验证码在 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'

检查邮箱(含 spam)看有没有收到真实验证码。

怎么修

  • 生产环境保持 MULTICA_DEV_VERIFICATION_CODE 为空,配好 Resend 后使用真实验证码
  • 本地开发或内网测试可以从 server 日志抄生成的验证码;如果需要 888888,设置 APP_ENV=developmentMULTICA_DEV_VERIFICATION_CODE=888888。不要在公网实例启用固定验证码(详见 登录与注册配置 → 固定本地测试验证码

Usage 看板一直是 0

症状:agent 执行完任务、原始的 token 用量已经写入数据库,但 Settings → UsageSettings → Runtime 上输入 / 输出 / 成本全部显示 0。没有任何报错——这是静默故障。

可能原因

  1. rollup_task_usage_hourly() 没被认领 —— Usage / Runtime 看板读的是派生表 task_usage_hourly,这张表必须靠 rollup_task_usage_hourly() 周期性填充。从 MUL-2957 起后端通过 DB 后端调度器(sys_cron_executions)在进程内跑 rollup;旧版本 binary、未应用 migration 113、或者所有副本长时间下线,都可能让这张表里没有最近的 SUCCESS 行。
  2. pg_cron 作为兼容路径配着、但指向了错的库 —— pg_cron.database_name 默认是 postgres;如果你的 Multica 数据库名不是 postgres,调度任务根本看不到 rollup_task_usage_hourly()。进程内调度器不依赖这一项,但如果你刻意拿掉了进程内调度而靠 pg_cron,DB 名就必须对得上。
  3. handler 被认领了、但静默报错 —— 比如 migration 没全部应用导致 SQL 函数缺失、或 DB role / search_path 配错了。看 sys_cron_executions 里的 FAILED 审计行。

怎么查

-- 确认原始数据有、hourly 表是空的
SELECT count(*) AS raw_rows FROM task_usage;
SELECT count(*) AS hourly_rows FROM task_usage_hourly;

-- 看进程内调度器的审计日志
SELECT plan_time, status, attempt, runner_id,
       error_code, error_msg, started_at, finished_at
  FROM sys_cron_executions
 WHERE job_name = 'rollup_task_usage_hourly'
 ORDER BY plan_time DESC
 LIMIT 20;

-- watermark —— 如果还是 1970-01-01,说明 rollup 从来没跑过
SELECT watermark_at FROM task_usage_hourly_rollup_state;

-- 兼容路径:以前注册过 pg_cron,确认装没装、指对了库没
SELECT * FROM pg_available_extensions WHERE name = 'pg_cron';
SHOW shared_preload_libraries;
SELECT jobname, schedule, database, active FROM cron.job;

怎么修

  • 确认至少一个后端副本里调度器真的在跑 —— 每 30 秒应该往 sys_cron_executionsrollup_task_usage_hourly 加一条 SUCCESS 行。
  • 手动跑一次 SQL 验证函数本身没问题:SELECT rollup_task_usage_hourly(); —— 刷新看板;如果数字出来了,SQL 这层 OK,问题在调度器认领路径上。
  • 如果 migration 113_sys_cron_executions 还没应用,重启后端让 migration 跑一遍,或手动 migrate up
  • 历史里有遗留的 pg_cron 入口也没事 —— SQL 函数里还持有 advisory lock 4246,应用调度器和 pg_cron 不会双写;要清掉冗余项见 Self-host 快速上手 → 用量汇总 里的 cron.unschedule

migration 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

可能原因:这是 migration 103 的 fail-closed guard。它要求 task_usage_hourly 已经追平了原始的 task_usage 之后,才允许丢掉旧的 daily rollup。只要数据库里有历史数据、且 rollup watermark 还停在 epoch(说明还没把历史回填进 hourly 表),这条 guard 就会拦住。

从 MUL-2957 起,migrate 命令在应用 migration 103 之前会自动跑一次幂等的按月切片 backfill(advisory lock 4246 保护),所以 v0.3.4 → v0.3.5+ 直升一次 migrate up 就能搞定。如果你还看到这个错,要么用的是 MUL-2957 之前的二进制,要么 hook 自己也失败了 —— 看 migrate 日志里更早一行的 task_usage hourly rollup hook 看具体原因。

怎么修

  1. 如果你跑的是 MUL-2957 之前的 binary,又没办法先升级 binary,就对同一个数据库手动跑一次独立 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. 重新跑升级 —— 重启 backend 容器即可,启动时会自动跑 migration。Guard 看到新的 watermark,103 就会通过。

  3. 之后由进程内调度器持续推 watermark —— 见 Self-host 快速上手 → 用量汇总

--sleep-between-slices=2s 在有多年历史的生产库上是个比较克制的默认值。如果你只想保留最近 N 个月、可以接受永久丢掉更老的桶,用 --months-back N --force-partial

端口冲突

症状multica servermultica daemon start 启动失败,报 address already in use

可能原因

  1. Server 端口被占用(默认 8080
  2. Daemon health 端口被占用(默认 19514,每个 profile 偏移一个 hash 值)
  3. Web dev server 端口冲突3000 / 5173
  4. 端口权限不足(绑 < 1024 的 privileged port 需要 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)container stdoutdocker logs -f <container>
服务器(systemd)journaljournalctl -u multica-server -f
前端(dev)pnpm dev 所在终端直接看
前端(browser)DevTools → ConsoleF12

需要更详细的 daemon 日志,把它从后台挪到前台跑:multica daemon stop && multica daemon start --foreground