Predictor X

Устранение неполадок

Распространенные проблемы при самостоятельном хостинге (self-hosting) Предиктор Икс — симптомы, причины, диагностика и способы решения.

Ищите проблему по ее симптомам. Каждая запись содержит разделы: симптом / вероятные причины / диагностика / решение. Если вашей ситуации нет в списке, создайте обращение (issue) на GitHub.

Демон не может подключиться к серверу

Симптом: команда status для predictorx daemon показывает offline или connection refused; в логах сервера отсутствуют запросы /api/daemon/register или /api/daemon/heartbeat. О том, как устроен механизм демона, читайте в разделе Демон и среды выполнения.

Вероятные причины:

  1. PREDICTORX_SERVER_URL указывает на неверный адрес — значение по умолчанию: ws://localhost:8080/ws; при самостоятельном хостинге необходимо изменить его на адрес вашего сервера.
  2. Блокировка сетью / брандмауэром — демон и сервер находятся в разных сетях или заблокирован исходящий трафик.
  3. Токен истек или недействителен — вы не выполнили команду predictorx login или персональный токен доступа (PAT) был отозван.
  4. Сервер отклонил регистрацию — учетная запись, под которой вы вошли, не входит в целевое рабочее пространство (регистрация возвращает ошибку 403).
  5. Ошибка разрешения DNS — имя хоста не разрешается на машине с демоном.

Диагностика:

predictorx daemon logs --lines 100    # поиск ошибок на стороне демона
echo $Предиктор Икс_SERVER_URL          # проверка установленного адреса
curl -i http://<server-host>:8080/health   # прямой запрос к серверу
curl -i http://<server-host>:8080/readyz  # проверка готовности БД и миграций
cat ~/.predictorx/config.json        # проверка наличия api_token
predictorx workspace list            # подтверждение членства в целевом рабочем пространстве

Решение: устраните соответствующую причину. Два наиболее частых решения: изменение PREDICTORX_SERVER_URL с перезапуском демона (predictorx daemon restart) и повторный вход в систему (predictorx logout && predictorx login).

Задачи зависли в статусе queued (в очереди)

Симптом: после назначения задачи агенту ее статус сразу меняется на in_progress, однако в течение долгого времени на странице нет признаков выполнения задачи агентом; при этом predictorx daemon status показывает, что демон находится в сети (online).

Вероятные причины (в порядке частоты):

  1. Достигнут лимит одновременных задач агента — параметр max_concurrent_tasks этого агента (по умолчанию 6) полностью исчерпан другими запущенными задачами.
  2. Другая задача того же агента все еще выполняется по этой же проблеме — задачи связки «тот же агент × та же проблема» выполняются строго последовательно во избежание дублирования.
  3. Агент был архивирован — после архивации новые задачи продолжают ставиться в очередь, но не могут быть обработаны и завершаются по тайм-ауту через 5 минут (код ошибки: G-01).
  4. Демон не зарегистрировал эту среду выполнения в текущем рабочем пространстве — перезапустите демон или заново выберите среду выполнения в интерфейсе.
  5. Демон отключился — не было сигналов проверки активности (heartbeat) за последние 45 секунд. Статус online в выводе daemon status может отражать лишь недавнее состояние до отключения.

Диагностика:

predictorx daemon status --output json       # список сред выполнения + last_seen_at
predictorx agent list                         # проверка, не архивирован ли агент
predictorx issue show <issue-id>             # просмотр истории задачи

На стороне сервера (при self-hosting) выполните поиск по ключевым словам "no_tasks" / "no_capacity" в логах, чтобы увидеть результат запроса задачи.

Решение:

  • Превышен лимит параллельных задач → дождитесь завершения текущих задач или увеличьте лимит с помощью команды predictorx agent update <id> --max-concurrent-tasks 10.
  • Последовательное выполнение по одной проблеме → дождитесь завершения предыдущей задачи или назначьте другого агента.
  • Агент архивирован → восстановите его командой predictorx agent restore <id>.
  • Среда выполнения не зарегистрирована → выполните predictorx daemon restart для повторной регистрации демона.

Не удается установить соединение WebSocket

Симптом: в консоли браузера выводится ошибка WebSocket is closed; на странице не обновляются данные в реальном времени (прогресс задач, комментарии, входящие), для их отображения требуется перезагрузка страницы; при этом фоновые задачи продолжают выполняться.

Вероятные причины:

  1. Ошибка проверки источника (Origin check) — домен вашего фронтенда отсутствует в белом списке CORS сервера. По умолчанию в белый список входят только localhost:3000/5173/5174; при размещении в публичном интернете необходимо настроить переменную FRONTEND_ORIGIN.
  2. Несовпадение протоколов — фронтенд на https:// требует использования wss://; для HTTP используется ws://.
  3. Обратный прокси-сервер не выполняет обновление (Upgrade) соединения до WebSocket — Nginx / Envoy / HAProxy по умолчанию не пересылают заголовок Upgrade.
  4. Cookie-файл JWT истек или отсутствует — не выполнялся повторный вход после 30-дневного периода действия сессии.

Диагностика:

  • Инструменты разработчика в браузере → вкладка Network (Сеть) → фильтр "WS" → проверка состояния соединения и кода ответа.
  • Поиск в логах сервера по ключевым словам "rejected origin" / "websocket" — ошибка источника (Origin) будет явно указана.
  • Запрос curl -i http://<server-host>:8080/ws должен возвращать код 101 Switching Protocols (с заголовком Upgrade).

Решение:

  • Неверный источник → укажите FRONTEND_ORIGIN=https://predictorx.yourdomain.com в файле .env сервера (или перечислите их через запятую в CORS_ALLOWED_ORIGINS) и перезапустите сервер.
  • Несовпадение протоколов → убедитесь, что протокол в FRONTEND_ORIGIN соответствует протоколу фронтенда.
  • Обратный прокси-сервер → для Nginx добавьте директивы: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";.
  • Истек срок действия cookie → обновите страницу и войдите в систему заново.

Не приходят электронные письма

Симптом: после отправки адреса электронной почты при входе или принятии приглашения код подтверждения не приходит ни во входящие, ни в спам.

Сначала определите, какой провайдер активен по мнению сервера. При запуске бэкенд выводит одну из следующих строк:

  • 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

Вероятные причины:

  1. Не задан RESEND_API_KEY — сервер автоматически переходит в режим отладки и выводит код в свой собственный поток stdout без вызова ошибки. Часто случается в продакшене.
  2. Ключ Resend API недействителен или исчерпан лимит — в логах сервера отображается ошибка "failed to send verification code".
  3. Домен отправителя RESEND_FROM_EMAIL не верифицирован в Resend — сервис отклоняет отправку.
  4. Письмо отправлено, но отмечено как спам почтовым провайдером получателя — проверьте панель управления Resend и папку со спамом.

Диагностика:

  • Поиск в логах сервера по строке "[DEV] Verification code for" — если она есть, значит Resend не настроен, а код выведен в stdout.
  • Панель управления Resend → раздел "Emails" для проверки истории отправки.
  • Убедитесь, что домен из RESEND_FROM_EMAIL присутствует в списке "Verified Domains" в консоли Resend.

Решение:

  • Отсутствует API-ключ → настройте его согласно инструкции Настройка авторизации и регистрации → Как работает почта и перезапустите сервер.
  • Домен не верифицирован → пройдите процедуру проверки DNS в консоли Resend (добавьте записи SPF / DKIM).
  • В экстренных случаях (при внутреннем тестировании) → скопируйте сгенерированный код из логов сервера, найдя строку с [DEV].

Если активным провайдером является SMTP

При использовании SMTP любая ошибка сопровождается указанием этапа, на котором произошел сбой, поэтому логи сервера сразу укажут, где именно реле отклонило сессию. Выполните поиск по строкам "failed to send verification email" / "failed to send invitation email" и проанализируйте ошибку:

Ошибка в логахЧто это означаетКак исправить
smtp dial <host>:<port>: dial tcp …: connect: connection refused / i/o timeoutКонтейнер бэкенда не может связаться с реле — неверный хост, порт, брандмауэр или реле не принимает входящие подключенияУбедитесь, что SMTP_HOST / SMTP_PORT разрешаются внутри контейнера (docker compose -f docker-compose.selfhost.yml exec backend nslookup <host> и nc -vz <host> <port>); откройте брандмауэр от хоста с Предиктор Икс до реле
smtp starttls: x509: certificate signed by unknown authority (или certificate is not valid for any names)Реле использует приватный CA / самоподписанный сертификат, который отклоняется хранилищем доверенных сертификатов контейнераУстановите корневой сертификат (CA) в контейнер или временно установите SMTP_TLS_INSECURE=true (только если вы уверены в безопасности сетевого сегмента до реле)
smtp auth: 535 5.7.8 Authentication credentials invalid (или 534/530)Неверные значения SMTP_USERNAME / SMTP_PASSWORD или реле требует отличный от PLAIN механизм авторизацииПерепроверьте учетные данные сервисного аккаунта у вашего почтового администратора; для анонимного внутреннего реле Exchange оставьте оба поля пустыми (SMTP_USERNAME=, SMTP_PASSWORD=)
smtp MAIL FROM: 550 5.7.1 Client does not have permissions to send as this senderРеле не принимает RESEND_FROM_EMAIL в качестве отправителя конверта (envelope sender) — стандартная ошибка Exchange "anonymous users not allowed" или проблема выравнивания DMARCУкажите в RESEND_FROM_EMAIL домен, который разрешен на реле; в Exchange предоставьте права ms-Exch-SMTP-Accept-Any-Sender для IP-адреса источника на принимающем коннекторе
smtp RCPT TO <addr>: 550 5.7.1 Unable to relayПринимающий коннектор реле запрещает вашей подсети отправку внешним получателям (частая проблема анонимных внутренних реле при попытке отправить почту на внешние домены)Ограничьте приглашения только внутренними адресатами или добавьте подсеть хоста Предиктор Икс в список разрешений Exchange "Anonymous Users → Relay"
smtp DATA / smtp write body / smtp end dataСессия была установлена, но реле отклонило тело письма — обычно из-за ограничений на размер сообщения, контентной фильтрации или обрыва соединения посередине передачиПроверьте логи реле на наличие соответствующего Message-ID (в логах пишется как <unixnano>@<host>); при необходимости увеличьте лимит на размер сообщения

Ошибки MAIL FROM, RCPT TO и DATA всегда логируются с кодом ответа реле, что позволяет сопоставить их с логами Exchange / Postfix на другой стороне. Коды подтверждения и токены приглашений никогда не включаются в текст выводимой ошибки.

Диагностика:

  • Найдите строку "EmailService: SMTP relay" при запуске, а затем ошибки "failed to send" для анализа сбоев во время работы.
  • Проверьте доступность изнутри контейнера бэкенда: 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_ (пароль отобразится в выводе — запускайте только в доверенной оболочке).

Решение:

  • Неверный хост или порт → скорректируйте SMTP_HOST / SMTP_PORT и перезапустите бэкенд; поддерживаемые режимы реле описаны в Настройка авторизации → Вариант Б: SMTP-реле.
  • Ошибка сертификата → добавьте CA вашего почтового реле в контейнер или временно пропишите SMTP_TLS_INSECURE=true в доверенном сегменте сети.
  • Ошибка авторизации → проверьте учетные данные; для анонимного внутреннего реле оставьте SMTP_USERNAME и SMTP_PASSWORD пустыми.
  • Ошибка Unable to relay → ограничьте отправку только внутренними адресами или предоставьте IP-адресу хоста Предиктор Икс права на отправку (relay) на коннекторе Exchange.

Не работает фиксированный локальный код тестирования

Симптом: на self-hosted инстансе вы пытаетесь войти с фиксированным локальным кодом (например, 000999), но сервер возвращает ошибку invalid or expired code (неверный или истекший код).

Вероятные причины (взаимоисключающие):

  1. Переменная PREDICTORX_DEV_VERIFICATION_CODE пуста — фиксированные коды отключены по умолчанию.
  2. Установлено значение APP_ENV=production — это правильная конфигурация для продакшена; фиксированные тестовые коды в режиме production игнорируются.
  3. Длина настроенного кода не равна 6 цифрам — система принимает только 6-значные значения.

Диагностика:

cat .env | grep -E 'APP_ENV|Предиктор Икс_DEV_VERIFICATION_CODE'
docker exec <container> env | grep -E 'APP_ENV|Предиктор Икс_DEV_VERIFICATION_CODE'

Проверьте ваш почтовый ящик (включая папку "Спам") на предмет получения настоящего кода подтверждения.

Решение:

Дашборд использования ресурсов показывает нули

Симптом: агенты успешно выполняют задачи, данные об использовании токенов записываются в базу данных, однако в разделах Настройки → Использование (Usage) и Настройки → Среды выполнения (Runtime) отображаются нули для входящих/исходящих токенов и стоимости. При этом в логах бэкенда нет никаких сообщений об ошибках.

Вероятные причины:

  1. Функция rollup_task_usage_hourly() никогда не вызывается — дашборды "Использование" и "Среда выполнения" считывают данные из агрегированной таблицы task_usage_hourly, которая заполняется этой функцией. Начиная с версии MUL-2957, бэкенд запускает процесс агрегации (rollup) внутри процесса через встроенный планировщик задач в БД (sys_cron_executions). Устаревшая сборка, отсутствие миграции 113 или длительный сбой бэкенда (когда не осталось ни одной работающей реплики) могут привести к тому, что в таблице планировщика не будет недавней записи со статусом SUCCESS.
  2. Расширение pg_cron включено для совместимости, но указывает на неверную базу данных — параметр pg_cron.database_name по умолчанию равен postgres. Если ваша база данных Предиктор Икс называется по-другому, запланированная задача никогда не увидит функцию rollup_task_usage_hourly(). Встроенный планировщик от этого не зависит, но если вы отключили встроенный планировщик и полагаетесь на pg_cron, имя БД должно совпадать.
  3. Обработчик вызывается, но завершается с ошибкой без вывода в логи — например, отсутствует SQL-функция из-за частично примененных миграций либо неверно настроены роли БД или search_path. Проверьте строки со статусом FAILED в таблице sys_cron_executions.

Диагностика:

-- Проверьте, существуют ли исходные события и пуста ли часовая таблица.
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, то агрегация ни разу не запускалась.
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 секунд он должен добавлять строку SUCCESS в sys_cron_executions для задачи rollup_task_usage_hourly.
  • Вызовите функцию агрегации вручную для проверки корректности SQL-пути: SELECT rollup_task_usage_hourly(); — обновите дашборд. Если цифры появились, значит, SQL-функция исправна, а проблема кроется в планировщике.
  • Если миграция 113_sys_cron_executions еще не была применена, перезапустите бэкенд для автоматического выполнения миграций или примените их вручную с помощью команды migrate up.
  • Если у вас осталась история от старых версий pg_cron (до появления встроенного планировщика), SQL-функция все еще удерживает рекомендательную блокировку (advisory lock) 4246, что предотвращает двойную запись. См. руководство Быстрый старт для self-host → Агрегация использования для очистки с помощью cron.unschedule.

Миграция 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. Он запрещает удаление старых ежедневных агрегаций до тех пор, пока таблица task_usage_hourly не будет синхронизирована с исходными данными task_usage. Защита срабатывает, если в БД есть записи, но отметка времени агрегации (watermark) все еще находится на начальной эпохе (1970-01-01) — то есть исторические данные еще не были перенесены в часовую таблицу.

Начиная с версии MUL-2957, команда миграции автоматически запускает идемпотентный перерасчет данных по месяцам (под рекомендательной блокировкой 4246) непосредственно перед применением миграции 103, поэтому обновление с v0.3.4 на v0.3.5+ выполняется за один шаг migrate up. Если вы все еще видите эту ошибку, вы используете сборку бэкенда до MUL-2957 либо сам обработчик завершился ошибкой (проверьте логи миграции на наличие строки task_usage hourly rollup hook).

Решение:

  1. Если вы используете версию до MUL-2957 и не можете сначала обновить сам бинарный файл бэкенда, запустите автономный скрипт перерасчета (backfill) на той же БД (он идемпотентен, его можно безопасно прерывать и перезапускать):

    # Docker Compose
    docker compose -f docker-compose.selfhost.yml exec backend \
      ./backfill_task_usage_hourly --sleep-between-slices=2s
    
    # Kubernetes
    kubectl -n predictorx exec deploy/predictorx-backend -- \
      ./backfill_task_usage_hourly --sleep-between-slices=2s
  2. Снова запустите процесс обновления — для этого достаточно перезапустить контейнер бэкенда (миграции выполняются при старте). Теперь защита увидит актуальную отметку времени и позволит применить миграцию 103.

  3. Встроенный планировщик продолжит автоматически обновлять отметку времени — подробнее см. Быстрый старт для self-host → Агрегация использования.

Параметр --sleep-between-slices=2s — это щадящий режим для продакшен-баз с многолетней историей. Используйте параметры --months-back N --force-partial, если хотите сохранить данные только за последние N месяцев и готовы навсегда удалить более старые агрегации.

Конфликты портов

Симптом: запуск predictorx server или predictorx daemon start завершается с ошибкой address already in use (адрес уже используется).

Вероятные причины:

  1. Занят порт сервера (по умолчанию 8080).
  2. Занят порт проверки состояния демона (по умолчанию 19514, смещается на основе хэша профиля).
  3. Конфликт портов веб-сервера разработки (3000 / 5173).
  4. Недостаточно прав для привязки к порту (для привязки к портам < 1024 требуются права суперпользователя sudo).

Диагностика:

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

Решение:

  • Завершите конфликтующий процесс (kill -9 <PID>) или измените порт с помощью переменной PORT=9000.
  • Для использования портов 80 / 443 → не привязывайте сервер к ним напрямую; настройте обратный прокси-сервер (Nginx / Caddy), который будет перенаправлять трафик на порт из пользовательского диапазона.

Где искать логи

КомпонентРасположениеКоманда для просмотра
Демон~/.predictorx/daemon.log (в фоновом режиме) или stdout (в интерактивном режиме)predictorx daemon logs -f --lines 100
Сервер (Docker)Стандартный вывод контейнера (stdout)docker logs -f <container>
Сервер (systemd)Журнал системы (journal)journalctl -u predictorx-server -f
Фронтенд (разработка)Терминал, в котором запущена команда pnpm devЧтение напрямую из терминала
Фронтенд (браузер)Инструменты разработчика → вкладка Console (Консоль)Нажмите клавишу F12

Для получения более подробных логов демона переведите его из фонового режима в интерактивный: predictorx daemon stop && predictorx daemon start --foreground.