# DEV TASK — 2 бага конвейера, вскрытые боевым запуском ET-006 **Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500) **Ветка:** `fix/pipeline-start-bugs` из свежего main (`git checkout main && git pull && git checkout -b fix/pipeline-start-bugs`). ⚠️ **ГРАБЛЯ push (ORCH-7):** после push `git log origin/main..origin/fix/pipeline-start-bugs` ДОЛЖЕН показать коммиты ДО отчёта «PR готов». ℹ️ Токен Gitea для PR: `docker exec orchestrator printenv ORCH_GITEA_TOKEN`. ## Контекст Первый боевой запуск задачи по новой статусной модели (триггер In Progress) вскрыл 2 бага. Триггер, токены, авторство — работают. Падает старт конвейера. --- ## БАГ 1 — `issue.updated` без description → QG-0 валит задачу в Blocked **Симптом:** перевод задачи в In Progress → `start_pipeline` → QG-0 «Description слишком короткий (нужно >= 20 символов)» → задача в Blocked, хотя описание в Plane ЕСТЬ. **Причина (`src/webhooks/plane.py`, ~строка 250 `start_pipeline`):** ```python description = data.get("description_stripped", data.get("description", "")) ``` Plane на webhook `issue.updated` (смена статуса) шлёт payload только с ИЗМЕНЁННЫМИ полями — `description`/`description_stripped` пустые/отсутствуют. На `work_item.created` они были, на `updated` — нет. → QG-0 видит пустое описание → fail → `set_issue_blocked`. **Фикс:** в `start_pipeline`, если `description` из payload пустой/короткий — дотянуть полное описание из Plane через GET API issue detail (тот же токен/паттерн, что уже используется для PATCH/комментов — см. `src/plane_sync.py`, там есть GET issue). Брать `description_stripped` (или стрипать `description_html`). НЕ создавать свой токен, использовать существующий механизм plane_sync. - ⚠️ GET issue в plane_sync, скорее всего, уже есть (handle_status_start логировал `GET .../issues// 200`). Используй его, верни description. - Если и из API пусто — тогда честный QG-0 fail (это валидный кейс пустого тикета). ## БАГ 2 — коллизия work_item_id + рассинхрон ветки/worktree **Симптом:** `ET-006` выдан ПОВТОРНО двум разным тикетам. task 8 (plane `9884fb9c`, 22 мая, branch `feature/ET-006-gpx-upload`, done) и task 25 (plane `bfb4866c`, сегодня, branch `feature/ET-006-popup-enduro-trails`). Analyst run 59 (task 25) писал артефакты в worktree task 8 (`feature_ET-006-gpx-upload`), потому что worktree/путь резолвится по `work_item_id`, а он совпал. Гейт `check_analysis_complete` искал артефакты в ветке task 25 → не нашёл → не перевёл в In Review → задача зависла. **Две связанные проблемы:** **2a. `get_next_work_item_id` переиспользует seq.** (см. M-6 — derive work_item_id from Plane sequence_id). Plane sequence_id может совпасть для разных проектов/после удаления, ИЛИ маппинг не учитывает уже выданные id. Нужно гарантировать **уникальность work_item_id в рамках репозитория**: если `ET-006` уже есть в БД tasks (любой stage) → не выдавать его снова, взять следующий свободный (ET-007, ET-008...). Глянь текущую логику `get_next_work_item_id` / M-6 derivation и добавь проверку коллизии по таблице tasks. - ⚠️ M-6 в списке «не ломать» как ЛОГИКА derive, но проверка-на-дубль — это надстройка, не замена. Согласуй: добавь uniqueness-guard ПОВЕРХ M-6, не переписывая derive. **2b. worktree/путь привязан к work_item_id, а не к task_id.** Из-за этого два таска с одним work_item_id делят один worktree. Привяжи worktree/ветку к уникальному ключу (task_id или plane_id-производный slug), чтобы коллизия work_item_id физически не могла увести агента в чужой worktree. Глянь `ensure_worktree` / `git_worktree.py` — как формируется путь. - Если фикс 2a гарантирует уникальность work_item_id — 2b становится защитой в глубину. Сделай ОБА (2a как первичный фикс, 2b как страховка), но если 2b затрагивает много кода — минимум добавь assert/лог при коллизии worktree. --- ## Ограничения - 🚫 НЕ трогай: nginx, openclaw.json, .env-секреты, deploy-хук, HMAC/verify_plane_signature, очередь (queue_worker/jobs — кроме чтения), webhook URL в БД Plane (уже починен ассистентом на `http://172.21.0.1:8500/webhook/plane`), STAGE_TRANSITIONS логику, per-agent authorship, GET/PATCH общий токен. - M-6 derive — не переписывать, добавить uniqueness-guard поверх. - Baseline тестов: см. прошлый прогон (190 passed + 9 pre-existing). Не ломать, conftest-глушилку Telegram (`tests/conftest.py`) НЕ трогать. - Conventional Commits, отдельные коммиты: `fix(webhook) fetch description from Plane API on status-start`, `fix(work-item) prevent work_item_id collision`, (опц.) `fix(worktree) bind path to task_id`. ## Тесты (контейнер) `IMG=$(docker inspect orchestrator --format '{{.Config.Image}}'); docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q` - `test_status_start_fetches_description`: issue.updated с пустым description в payload → start_pipeline тянет описание из Plane API (мок GET) → QG-0 проходит. Мокать httpx. - `test_work_item_id_uniqueness`: если ET-006 уже в tasks → следующий тикет получает ET-007, не ET-006. - (если делаешь 2b) `test_worktree_per_task`: два таска с искусственно одинаковым work_item_id не делят worktree. ## Проверка (ассистент проверит вживую) | # | Что | Критерий | |---|-----|----------| | 1 | description из API | перевод в In Progress тикета с описанием → НЕ Blocked, стартует analyst | | 2 | уникальность id | новый тикет не получает уже занятый ET-NNN | | 3 | worktree | агент пишет в свою ветку, гейт находит артефакты, переход в In Review | | 4 | тесты | baseline не сломан + новые passed | | 5 | git | PR в main, remote содержит коммиты | ## Отчёт - НЕ деплоить, НЕ мержить (мерж — ассистент после живой проверки + повторного боевого запуска #6). - В отчёте: где именно правил (файл:функция), как тянешь description (какой GET-метод plane_sync переиспользовал), как гарантируешь уникальность work_item_id, вывод релевантных тестов. Если что-то разошлось с кодом/реальностью — отчитайся, не выдумывай. Один исполнитель.