diff --git a/family/documents/vault-sync/2026-10-15-http402-balance-outage-9d.md b/family/documents/vault-sync/2026-10-15-http402-balance-outage-9d.md new file mode 100644 index 00000000..8019fc07 --- /dev/null +++ b/family/documents/vault-sync/2026-10-15-http402-balance-outage-9d.md @@ -0,0 +1,101 @@ +--- +tags: [vault-sync, incident, cron, llm-provider] +date: 2026-10-15 +severity: high +status: resolved +--- + +# Инцидент: vault-sync стоял 8,93 суток — HTTP 402 Insufficient Balance (2026-10-06 → 2026-10-15) + +## Итог одной строкой + +Задача `sync-vault` (cron `*/5`, id `29851c413903`) **не выполнялась 8 дней 22,4 часа**: +2572 тика подряд упали с `RuntimeError: HTTP 402: Insufficient Balance` (провайдер +DeepSeek). Баланс пополнен ~2026-10-15 09:50 UTC — синк восстановился сам на очередном +тике, бэклог влит fast-forward, потерь данных нет. + +## Хронология (UTC) + +| Время | Событие | +|---|---| +| 2026-10-06 11:25:44 | последний успешный тик (в логе `[syncthing] Pushed ✓` 11:25:30) | +| 2026-10-06 ~11:30 | первый 402; за сутки 10-06 — 150 отказов, 138 успехов | +| 10-07 … 10-14 | ровно **288 отказов в сутки** = 100 % тиков, ни одного запуска скрипта | +| 10-15 00:00 … 09:45 | 118 отказов (все слоты до 09:45) | +| 2026-10-15 09:50:21 | тик **completed**, скрипт отработал все три фазы, бэклог влит | +| 2026-10-15 09:51 | `failure_streak` сброшен, /vault, bare, /obsidian-syncthing на одном SHA `43676802` | + +Длительность: 10-06 11:25:44 → 10-15 09:50:21 = **12 864,9 мин = 8,93 суток**. +Число подряд упавших тиков: **2572** (= `failure_streak` в `/opt/data/cron/jobs.json`). + +## Диагностика (что и где смотреть) + +1. **Разрыв в `/tmp/vault-sync.log`**: 10-06 11:25:30 → 10-15 09:50:21. Лог **не** + обнулялся (первая строка 2026-09-15 11:35:19) — значит контейнер не пересоздавался, + это отказ LLM-петли, а не потеря лога. +2. **`/opt/data/cron/jobs.json`** — самое быстрое доказательство: + `"failure_streak": 2572`, `"last_status": "error"`, + `"last_error": "RuntimeError: HTTP 402: Insufficient Balance (request_id: …)"`. + `failure_streak × 5 мин` = длительность простоя. +3. **`/opt/data/cron/executions.db`** — окно только за последние 1001 запись (≈3,5 суток): + в нём 1000 × `failed` с тем же текстом 402 и одна `running` (текущий тик). + Данные старее обрезаны, поэтому длительность по БД **не** измеряется. +4. **`/opt/data/cron/usage_audit.jsonl`** — полная хронология без обрезки (12 231 строка, + с 2026-09-02): `error` есть → тик не запускал скрипт; `error: null` → запускал. + Разбивка 402 по суткам: 10-06 — 150, 10-07…10-14 — по 288, 10-15 — 118. + +## Причина + +Задача — **prompt-задача** (`"prompt": "bash /opt/data/sync-vault.sh >> …"`), её исполняет +LLM-раунд. Нет баланса у провайдера → нет LLM-раунда → скрипт не вызывается вообще +(не «git упал», а «скрипт не стартовал»). Git-инфраструктура и bare-репозиторий при этом +были полностью живы: Eagle пушил в bare всё это время (12 коммитов 10-07, 18 — 10-08, +14 — 10-09, …, 42 — 10-14, 49 — 10-15). + +## Что не потеряно + +- bare всё это время принимал коммиты Eagle напрямую — история целая. +- Телефон (Syncthing) держал свои правки локально и отдал их сегодня коммитом + `57061535` (`personal/documents/*`, `personal/shopping-order.md`, `Расходы руб.md`). +- Первый удачный тик 10-15 09:50 влил бэклог: **178 коммитов** с 10-06 11:00, + `Merge … origin/main` + fast-forward, без конфликтов. +- Проверка: три SHA равны (`43676802…`) в `/vault`, `/vault.git`, `/obsidian-syncthing`; + `git status --porcelain` и `git ls-files -u` пусты в обоих клонах; новых + `*.sync-conflict-*` / `*.bak-*` за период нет. + +## Корневая проблема: стоимость prompt-тика + +Один тик этой «пустой» задачи съедает десятки-сотни тысяч prompt-токенов, потому что +агент собирает весь системный промпт и навыки на каждый запуск: + +- замер по `usage_audit.jsonl` за последние полные сутки (10-06): **138 успешных тиков, + 18 503 331 токен суммарно**, максимум **378 455 токенов за один тик**; +- при полной каденции 288 тиков/сутки это ≈ **38 млн токенов в сутки** ради + `git add/commit/merge/push`, который сам по себе стоит 0 токенов. + +Отсюда и опустошение баланса: 402-класс уже наблюдался 09-14, 09-21, 09-25, 09-28 — +это не разовый сбой, а режим, в котором счёт провайдера выедается задачей-синхронизатором. + +## Рекомендации (решение владельца) + +1. **Убрать LLM из петли** — основной фикс: + `hermes cron edit 29851c413903 --no-agent --script `. + Ловушка: `--script` ждёт файл под `~/.hermes/scripts/`, `/root/.hermes` в контейнере + не переживает рекреацию — нужен персистентный путь (симлинк на `/opt/data/`). + Тогда тик не зависит ни от баланса, ни от доступности провайдера. +2. Как минимум — зафиксировать модель/провайдера подешевле и урезать системный промпт + для этой задачи (`enabled_toolsets`, `skills: []` уже пусто, но промпт всё равно + собирается целиком). +3. Мониторинг: алерт при `failure_streak > 12` (час простоя) в `jobs.json` — + иначе отказ виден только по дырке в логе, а он «локальный» (`deliver: local`), + Алексею ничего не приходит. +4. `deliver: local` для этой задачи — правильно (не спамить), но именно поэтому + 9-дневный отказ прошёл незамеченным. Стоит сделать отдельный health-check-тик, + который при простое шлёт одно сообщение в Telegram. + +## Оценка класса + +Предыдущие эпизоды 402: 09-21 — 6 тиков (35 мин), 09-25 — 53, 09-28 — 14. +Текущий — **2572 тика / 8,93 суток**, на два порядка больше. Разница не в механизме, +а в том, что баланс не пополняли: провайдер отвечает 402 мгновенно (1,7–7,6 с на тик), +поэтому самовосстановления нет по определению — нужен внешний сигнал.