# Orchestrator — Журнал прогресса 2026-06-02 Большой день. Инцидент → root-fix → надёжная очередь. Хронология и итоги. --- ## 0. Контекст дня Создала в Plane задачи ORCH-1..7. Plane-webhook поймал каждую и авто-запустил конвейер — но без фильтра по проекту, всё ушло в неправильный репо `enduro-trails`, наплодив мусор ET-010..016. Подход: **стоп → чистка → защита → root-fix → надёжность**. --- ## 1. ИНЦИДЕНТ: webhook авто-запуск (19:00) ### Что случилось - Plane-webhook (id `93f0c342-a614-4248-9d0f-c107276f5620`) срабатывал на ЛЮБОЕ issue в workspace, без фильтра по проекту - `plane.py:91` hardcode `repo=settings.default_repo` → всё лилось в `enduro-trails` - Наплодило ветки/работу ET-010..016 в чужом репо - ⚠️ **Позитив:** автономность реально работает (analyst exit=0, auto-commit, worktree ORCH-2 сработал) — баг был в маршрутизации, не в движке ### Меры (предохранитель) - 🛡️ **Plane-webhook ДЕАКТИВИРОВАН** в Plane postgres: `UPDATE webhooks SET is_active=false` → проверено `is_active=f` - 🧹 Вычищено: ветки ET-010..016 (git local+remote), worktree `_wt/enduro-trails/*` (root-owned → sudo rm), тестовые iso-A/iso-B, tasks≥19 + agent_runs в БД орка - Plane чист: orchestrator=7, enduro=5 (родные) - Заметка: `docs/INCIDENT_2026-06-02_webhook_autorun.txt` ### Грабли (postgres-пароль) - `\x27`-экранирование кавычек через ssh+docker ЛОМАЕТСЯ → писать SQL в файл + `psql -f`, пароль из env контейнера - Plane postgres: контейнер `plane-app-plane-db-1`, db/user `plane` --- ## 2. ROOT FIX = ORCH-6 (Multi-repo) — ✅ ЗАМЕРЖЕН (PR #2) ### ТЗ `DEV_TASK_ORCH6_MULTIREPO.md` — реестр проектов (Plane id→repo+prefix) + фильтр webhook по проекту + resolve repo + plane_sync в правильный проект + prefix per project. ### Реализация (Dev, Opus 4.8) - `src/projects.py` — `ProjectConfig` + резолверы `get_project_by_plane_id`/`get_project_by_repo`/`known_plane_project_ids` - Реестр: enduro→`enduro-trails`/ET, orchestrator→`orchestrator`/ORCH, unknown→игнор - Затронуты: config.py, plane.py, db.py (`get_next_work_item_id` per project), plane_sync.py, gitea.py - ⚠️ Первый прогон упал по таймауту LLM на ~75% — дослала добивку (НЕ начинала заново, рабочее не теряем) ### Проверено мной вживую + замержено - ✅ PR #2 смержен (merge commit `b021ff7`), main пересобран, health ok - ✅ 57 passed (20 новых), 9 fail — pre-existing baseline цел - ✅ **Plane-webhook ВКЛЮЧЁН обратно** (`is_active=t`) - ✅ Боевой тест защиты на проде: 1. HMAC-подпись: без валидной → `401 Invalid signature` 2. Фильтр проекта: валидная подпись + unknown проект → `200 {"status":"ignored","reason":"unknown project"}`, лог `known: 2` 3. Задача НЕ создалась (11→11) — **инцидент технически невозможен** --- ## 3. ORCH-1 (F-2b) — Персистентная очередь задач — ✅ БАЗА ГОТОВА (PR #3) ### Проблема (in-process) - `launcher.launch()` синхронно: `Popen(claude)` + 2 daemon-thread (watchdog 1800с + monitor: ждёт/коммитит/advance) - 8 точек: `plane.py:189,234,308,389` + `gitea.py:126,203,275,300` - Беды: рестарт = агенты-сироты + потеря работы; нет лимита параллелизма; нет ретраев; webhook блокируется ### Реализация (Dev, Opus 4.8) — ТЗ `DEV_TASK_ORCH1_QUEUE.md` - Таблица `jobs` (queued/running/done/failed) + atomic `claim_next_job` + хелперы (enqueue/mark/count/requeue/get/status_counts/recent) - `src/queue_worker.py` — drain-loop + `max_concurrency` + graceful stop - `launcher.launch_job()` + `_finalize_job` (done/requeue/failed+Telegram) - `main.py` lifespan: queue-recovery (requeue running на старте) после M-1 - Webhook-хэндлеры (8 точек) → `enqueue_job` - `GET /queue` эндпоинт - config: `max_concurrency=1`, `queue_poll_interval=2.0` - Доки на сервере: `docs/ARCHITECTURE.md`, `docs/ORCH-1_JOB_QUEUE.md` ### Проверено мной вживую - ✅ 76 passed (19 новых, вкл. атомарность claim на 8 потоках/20 jobs), 9 pre-existing fail - ✅ `/queue` живой: counts, max_concurrency, recent - ✅ PR #3 open, mergeable:True - ✅ Фиксы B-1/B-2/M-1/ORCH-2/ORCH-6 не тронуты ### Метрики прогона - Dev `orch1_queue`: done, 12m26s, 337k токенов (in 294k / out 43k) ### ⚠️ Урок: resilience в базовый PR Dev НЕ встроил Моё уточнение (preflight/429/backoff/breaker) пришло когда Dev уже ушёл в финал → в коде его нет. **Правило:** при дослыле уточнения в активную сессию — проверять, что оно реально вошло в КОД, а не только в отчёт «done». --- ## 4. ORCH-1b (Resilience-слой) — ✅ ГОТОВ (в PR #3, проверен на проде) ### Итог (Dev `orch1b_resilience`, Opus 4.8) — проверено мной вживую - 7 коммитов поверх базы (4ef87a3..c23f000), запушено (local==remote), PR #3 mergeable - `preflight.py`, `error_classifier.py`, db `_ensure_column` (PRAGMA-safe), `compute_backoff`+Retry-After, `CircuitBreaker` - ✅ **110 passed** (26 новых resilience, всего 34 в test_resilience), 9 fail — pre-existing 401 - ✅ `/queue` отдаёт `resilience.breaker` (closed) + `preflight_ok:true` - ✅ preflight reason `2.1.142 (Claude Code)` — **токены НЕ потрачены** - ✅ классификатор: 429/overloaded→transient, traceback→permanent - ⚠️ Dev «остановился из осторожности» (решил что параллельная сессия пишет те же файлы — mtime менялись). Параллельной сессии НЕ БЫЛО — это его собственная активность. По факту всё довёл/запушил; пересборку сделала я. ### Идея Славы (отличный вопрос про надёжность claude CLI) Два РАЗНЫХ зверя, лечить раздельно: **A. CLI недоступен** (бинарь/сеть) → дешёвый preflight: - `os.path.exists(CLAUDE_BIN)` + `claude --version` (timeout 5с — токены НЕ жжёт) + опц. TCP до endpoint - Кэш ~45с, FAIL → воркер не claim'ит job (остаётся queued), не падает - 🚫 НИКАКОГО prompt-ping (ping→pong) перед каждым job — это трата лимита **B. Rate limit 429** → предсказать НЕЛЬЗЯ, ловить на ВЫХОДЕ: - Парсить log/stderr на `429`/`rate limit`/`overloaded`/`Retry-After`/`quota` - Классификатор transient vs permanent → разные ветки ретраев **C. Backoff** для transient: - Колонки `available_at` + `transient_attempts` в `jobs` - claim: `WHERE status='queued' AND (available_at IS NULL OR available_at<=now)` - Exp backoff `min(2^n*10, 600)с` + уважать `Retry-After` **D. Circuit breaker:** - 3 transient подряд → open: пауза 5 мин (CLI не дёргаем, лимит не выжигаем) + Telegram-алерт - half-open → 1 проба → ожил=closed, иначе снова open - отражать в `/queue` ### Config (добавляется) `preflight_cache_ttl=45`, `backoff_base_seconds=10`, `backoff_max_seconds=600`, `transient_max_attempts=5`, `breaker_threshold=3`, `breaker_pause_seconds=300` ### ⚠️ Урок: тест-алерты дёргают Славу Dev гонял retry-тест с синтетическим `repo r-retry`/job 3 → fail-алерт долетел в Telegram, Слава испугался («Ау»). Проверка: `/queue` по нулям, в прод-БД нет `r-retry` — жил только в эфемерной pytest-БД. **TODO ORCH-1b:** подавить Telegram-нотификации в тестовых прогонах (нотификации только прод). ### Требование к миграции БД Безопасная: PRAGMA table_info-проверка перед ALTER (prod-база `jobs` уже живая). --- ## 5. Итоги дня | Что | Статус | |-----|--------| | Инцидент webhook | 🟢 локализован, вычищен | | ORCH-6 multi-repo (root fix) | 🟢 замержен PR #2, проверен на проде | | Plane-webhook | 🟢 включён обратно, защита боевая | | ORCH-1 очередь | 🟢 база готова PR #3, проверена | | ORCH-1b resilience | 🟢 готов в PR #3 (110 passed), проверен на проде | ## 6. Следующие шаги - PR #3 (base + resilience) готов, проверен — **ждёт ОК Славы на мерж** - TODO: подавить Telegram-нотификации в pytest-прогонах (тест-алерты дёргают Славу) - Бэклог: ORCH-3 (S-2/S-3 rollback), ORCH-4 (M-3 stage-engine), ORCH-5 (M-7 idempotency/webhook dedup) ## 7. Висящие вопросы Славе - PR #19 (enduro-trails) — мержить? - PR #1 (orchestrator worktree) — мержить? - PR #2 (ORCH-6) — ✅ уже смержен