Files
orchestrator/docs/BUGFIXES_2026-06-02.md

85 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bugfixes 2026-06-02 — устранение багов оркестратора
**Источник:** `tasks/multi-agent/AUDIT_2026-06-02.md`
**Цель:** вернуть автономность мультиагентного pipeline (ET-009: 0/6 этапов были автономны).
**Исполнитель:** Dev-агент (Opus 4.8 Tokenator).
---
## Что починено
### B-1 — запись `.task-*.md` без docker
**Было:** `launcher._write_task_file()` писал файл через `docker run --rm -i python:3.12-slim bash -c "cat > ..."`. Бинарника `docker` в контейнере НЕТ → запись падала молча → агент читал старый task-файл.
**Стало:** прямая запись в смонтированный volume `/repos/<repo>/<task_file>` обычным `open(..., "w")`. При ошибке записи — `RuntimeError` (не молчит).
**Файл:** `src/agents/launcher.py` (`_write_task_file`, вызов в `launch`).
**Проверка:**
```bash
docker exec orchestrator python3 -c "
import sys; sys.path.insert(0,'/repos/orchestrator')
from src.agents.launcher import launcher
launcher._write_task_file('enduro-trails', '.task-test-write.md', 'hello-from-fix')
print(open('/repos/enduro-trails/.task-test-write.md').read())"
# => hello-from-fix (без docker)
```
✅ Verified: READBACK = `hello-from-fix`.
### B-2 — Popen stdout → файл, убран PIPE-поток (зомби, потеря exit_code)
**Было:** `Popen(stdout=PIPE)` + daemon-поток с `select`/`readline` + startup-timeout 120с. → PIPE-deadlock, зомби при рестарте, `exit_code=None` в БД (все прогоны ET-009).
**Стало:** `log_fh = open(output_path, "w")`; `Popen(stdout=log_fh, stderr=STDOUT)`. `_monitor_agent` упрощён до `proc.wait()` + `log_fh.close()`. PIPE-поток и startup-timeout удалены. Watchdog по pid (`AGENT_TIMEOUT`) сохранён.
**Файл:** `src/agents/launcher.py` (`launch`, `_monitor_agent`).
**Проверка:** после прогона `SELECT exit_code FROM agent_runs ORDER BY id DESC LIMIT 1` != NULL; `ps aux | grep defunct` — пусто.
### B-3 — `.task-*.md` в `.gitignore`, не коммитятся
**Было:** task-файлы трекались в git (`.task-arch.md`, `.task-dev.md`, `.task-review.md`, `.task.md`) и тащились между задачами.
**Стало:** в `enduro-trails/.gitignore` добавлено `.task*.md`; трекаемые файлы убраны из индекса (`git rm --cached`).
**Файл:** `enduro-trails/.gitignore` (+ untrack). Ветка `main` protected → изменения в **PR #19** (`chore/gitignore-task-files`).
**Проверка:** `git check-ignore .task.md .task-arch.md` → matched. `git add docs/ src/ tests/` (scoped) не цепляют task-файлы.
### S-5 — машиночитаемый verdict ревьюера
**Было:** `check_reviewer_verdict` искал подстроки `APPROVED`/`REQUEST_CHANGES` во всём тексте (5000 байт) → ложные срабатывания на таблицах.
**Стало:** читается ТОЛЬКО `verdict:` из YAML-frontmatter `12-review.md` (через `yaml.safe_load`). Нет verdict / нет frontmatter → not-approved. `reviewer.md` обновлён: требование frontmatter `verdict: APPROVED|REQUEST_CHANGES`.
**Файлы:** `src/qg/checks.py` (`check_reviewer_verdict`), `enduro-trails/.openclaw/agents/reviewer.md` (PR #19; рабочая копия применена сразу).
**Проверка:** ET-009 `12-review.md` (frontmatter `verdict: APPROVED`) → `(True, 'Reviewer verdict: APPROVED')`. Unit-тесты покрывают APPROVED/REQUEST_CHANGES/no-verdict/no-frontmatter/таблица-в-теле.
### S-1 — QG тестов гоняет сам оркестратор (не Gitea CI)
**Было:** `development → review` QG = `check_ci_green` (Gitea status). CI не настроен → всегда false → автопереход не происходил + ложные «CI failed» алерты.
**Стало:** новый QG `check_tests_local` — оркестратор делает `git fetch/checkout <branch>` + `make test` в `/repos/<repo>`, judge по exit-code. `stages.py`: `development` QG → `check_tests_local`. Dispatch добавлен в `launcher._try_advance_stage` и `webhooks/plane._try_advance_stage` (args `(repo, branch)`). `webhooks/gitea.handle_ci_status`: `failure` → debug-лог, без `notify_error`.
**Файлы:** `src/qg/checks.py`, `src/stages.py`, `src/agents/launcher.py`, `src/webhooks/plane.py`, `src/webhooks/gitea.py`.
**Грабля (известное ограничение):** `check_tests_local` делает checkout в shared `/repos` — небезопасно при параллельных задачах (S-4 worktree — отдельно).
### M-1 — нормальный orphan-recovery
**Было:** `UPDATE agent_runs SET exit_code=-1 WHERE finished_at IS NULL AND started_at < now-35min` — молча списывал зомби.
**Стало:** перечисляем каждый orphan-run, помечаем exit=-1, логируем per-run `warning` («manual check needed»), отправляем Telegram-уведомление. Не автоперезапускаем (риск зацикливания). Killing по pid невозможен — pid не персистится в БД (задокументировано).
**Файл:** `src/main.py` (lifespan).
---
## Что НЕ входило (отдельные задачи)
- S-2/S-3 (rollback деплоера в shared-репо), S-4 (git worktree per task), M-3 (единый stage-engine), F-2b (очередь задач), M-7 (идемпотентность webhook). `_auto_merge_pr` — мёртвый код оставлен (отдельная чистка).
## Тесты
- Новый файл `tests/test_launcher.py`: 10 тестов (`_write_task_file` пишет/raise/без docker; `check_reviewer_verdict` frontmatter cases).
- `tests/test_qg.py`: 16 passed. `tests/test_launcher.py`: 10 passed.
- ⚠️ Pre-existing: `tests/test_webhooks.py` имеет падения (401/signature + cross-file env pollution) — НЕ связаны с этими фиксами, существовали до правок. Запуск в изоляции part-passes; в общем прогоне больше падений из-за общего env/DB между тест-файлами. Гигиена test_webhooks — отдельная задача.
## Деплой
Оркестратор пересобран: `cd /home/slin/repos/orchestrator && docker compose up -d --build`. Health: `{"status":"ok"}`.
---
## Дополнительно найдено и починено в ходе теста автономности
### git safe.directory (launcher commit/push)
В ходе теста выяснилось: git внутри контейнера (root) над bind-mounted `/repos` падал с "dubious ownership" → авто-commit/push агента не проходил. Фикс: `git config --system --add safe.directory "*"` в Dockerfile. Теперь `_monitor_agent` commit+push работает автономно (проверено: `analyst(ET): auto-commit run_id=47` запушен в origin).
### init:true (PID-1 reaper) — добиваем B-2
Прямой child (bash) reap-ался корректно через `proc.wait()`, НО claude (node) порождает свои дочерние процессы; при выходе bash они реparent-ились на PID 1 (uvicorn), который их НЕ reap-ал → grandchild-зомби. Фикс: `init: true` в docker-compose.yml — Docker внедряет `docker-init`(tini) как PID 1. Проверено: после реального прогона агента `ZOMBIE_COUNT_AFTER=0`.
## Тест автономности (Task 9) — РЕЗУЛЬТАТ
Запуск через `launcher.launch("analyst", ...)` (НЕ base64). Подтверждено автономно:
- B-1: свежий `.task.md` записан без docker (which docker = NO_DOCKER_BINARY)
- B-2: `exit_code=0` в `agent_runs` (run 46/47/48)
- зомби: 0 после прогона (tini reaper)
- git: auto-commit + push в origin отработал
- M-1: при рестарте orphan-recovery залогировал per-run + Telegram (runs 42/43/44 ET-009)