---
name: ops-dashboard-autorestart-kills-long-jobs
description: "Глобальный auto_restart ops_dashboard убивает периодические job-ы, чей прогон длиннее cooldown_sec"
metadata: 
  node_type: memory
  type: project
  originSessionId: d0c6c5aa-4d05-42e2-a1f9-034482b88603
  modified: 2026-07-30T04:28:34.196Z
---

**Ловушка ops_dashboard.** Глобальный `auto_restart` в `dashboard_config.json` (`enabled: true, threshold: 3, cooldown_sec: 300`) наследуется КАЖДОЙ карточкой, у которой нет своего. Для долгого периодического job-а это замкнутый цикл: тайл красный (нет успеха за сутки) → дашборд делает `launchctl kickstart -k` → SIGTERM живому прогону → прогон стартует заново → за 5 минут не успевает → снова красный.

**Реальный случай (2026-07-27):** `tbr_closed_poller` (83 субъекта, полный прогон ~3 часа) не завершался ни разу с 26.07 07:11 — бенчмарк цен ТБР тихо переставал обновляться, а в Telegram каждые 5 минут падало «♻️ Auto-restart». В логе цикл виден по повторяющимся «DB engine created» ровно через 5:12.

**Правило:** карточке периодической задачи, чей прогон длиннее `cooldown_sec`, ставить `"auto_restart": {"enabled": false}` явно. Перезапуск не лечит суточную задачу: красный тайл означает «сутки не было успешного прогона», реакция — смотреть логи, а не убивать текущий прогон.

**Диагностика:** `launchctl list | grep <job>` показывает PID с последним кодом `-15` (SIGTERM) — это признак, что job убивают снаружи, а не он падает сам.

**Закрыто системно (2026-07-27):** в `ops_dashboard/server.py` добавлена `launchd_job_kind(label)` (читает plist: `KeepAlive` → daemon, `StartInterval`/`StartCalendarInterval` → periodic), и `maybe_auto_restart` пропускает периодические. Демоны перезапускаются как раньше, красный тайл и алерт у периодических сохраняются. Проверено на 8 реальных задачах. Ручное выключение `auto_restart` на карточке больше не требуется.

**Вторая ловушка того же корня — окно протухания (2026-07-30).** Порог «нет успеха дольше 24 часов» был константой в `service_snapshot`. Суточная задача, которая сама идёт часы и пишет успех в конце, гарантированно краснеет за секунды до финиша: у `tbr-closed-poller` вчерашний успех лёг в 07:19:12, сегодняшний в 07:19:15 — алерт «running → down» каждый день при полностью здоровом прогоне. Порог вынесен в поле карточки **`health_stale_sec`** (дефолт 86400), поллеру выставлено 108000 (30 ч). Правило для новых периодических карточек: окно = период расписания + длительность прогона + запас. Глушить сам переход в down НЕЛЬЗЯ — это единственный сигнал о пропущенном прогоне.

Родств.: [[tbr_map_reports_pivot]], [[ops_dashboard_tailscale_funnel]], [[ops_dashboard_config_audit_2026-07]].
