8.3 KiB
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):
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/<id>/ 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, вывод релевантных тестов. Если что-то разошлось с кодом/реальностью — отчитайся, не выдумывай. Один исполнитель.