# DEV_TASK_ORCHESTRATOR_FIXES.md — Фикс критических багов Orchestrator --- type: dev-task priority: high project: multi-agent created_at: 2026-05-21 author: stream --- ## Контекст Первый полный автоматический прогон ET-002 выявил 5 критических багов, которые мешают автономной работе пайплайна. Без этих фиксов каждый переход между стадиями требует ручного вмешательства. ## Задачи ### 1. Установить git в контейнер orchestrator **Файл:** `/home/slin/repos/orchestrator/Dockerfile` **Что сделать:** Добавить `git` в `apt-get install` (или `apk add git` если alpine). **Зачем:** Агенты (Claude CLI) не могут коммитить — используют dulwich (workaround), который коммитит от root и ломает permissions. **Проверка:** ```bash docker exec orchestrator git --version ``` ### 2. Агенты коммитят от правильного пользователя **Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py` **Что сделать:** В env для subprocess.Popen добавить: ```python env={ **os.environ, "HOME": "/home/slin", "GIT_AUTHOR_NAME": "claude-bot", "GIT_AUTHOR_EMAIL": "claude-bot@mva154.local", "GIT_COMMITTER_NAME": "claude-bot", "GIT_COMMITTER_EMAIL": "claude-bot@mva154.local", } ``` **Зачем:** Коммиты от root ломают `.git/objects` permissions. С git env vars коммиты будут от claude-bot. **Проверка:** ```bash docker exec orchestrator bash -c 'HOME=/home/slin GIT_AUTHOR_NAME=claude-bot git -C /repos/enduro-trails log --format="%an" -1' ``` ### 3. Привязать task_id к agent_runs **Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py` **Что сделать:** Метод `launch()` должен принимать `task_id: int | None = None` и передавать его в INSERT: ```python def launch(self, agent: str, repo: str, task_content: str = None, task_id: int = None) -> int: ... cursor = conn.execute( "INSERT INTO agent_runs (task_id, agent) VALUES (?, ?)", (task_id, agent), ) ``` **Файл:** `/home/slin/repos/orchestrator/src/webhooks/plane.py` **Что сделать:** В месте вызова `launcher.launch(agent, repo, task_desc)` добавить `task_id=task_id`: ```python run_id = launcher.launch(agent, repo, task_desc, task_id=task_id) ``` **Проверка:** ```bash docker exec orchestrator python -c " import sqlite3 conn = sqlite3.connect('/app/data/orchestrator.db') conn.row_factory = sqlite3.Row for r in conn.execute('SELECT id, task_id, agent FROM agent_runs ORDER BY id DESC LIMIT 3').fetchall(): print(dict(r)) " ``` Ожидание: `task_id` не NULL для новых runs. ### 4. Автоматическое продвижение stage после CI green (без `:approved:`) **Файл:** `/home/slin/repos/orchestrator/src/webhooks/gitea.py` **Что сделать:** При получении `status` event с `state=success`: 1. Найти task по branch 2. Если task в stage `development` и QG `check_ci_green` проходит → автоматически продвинуть в `review` и запустить Reviewer 3. Аналогично: если task в stage `review` и push содержит review file → проверить QG и продвинуть **Логика:** ```python # В handle_status или handle_push: if task.stage == "development" and ci_state == "success": # Check QG passed, reason = check_ci_green(repo, branch) if passed: advance_stage(task_id, "development", "review") launch_reviewer(task_id, repo, branch) ``` **Зачем:** Сейчас после CI green Orchestrator просто логирует "waiting for CI" и ничего не делает. Нужен `:approved:` webhook для продвижения. По BRD это должно быть автоматически. **Проверка:** Push в feature-ветку → CI green → stage автоматически меняется на `review` без ручного webhook. ### 5. Timeout для агентов (30 минут) **Файл:** `/home/slin/repos/orchestrator/src/agents/launcher.py` **Что сделать:** Добавить background thread/asyncio task который: 1. Через 30 минут после запуска проверяет — жив ли процесс 2. Если жив — kill -9 3. Записывает в agent_runs: `exit_code=-9, finished_at=now()` 4. Логирует warning **Вариант реализации:** ```python import threading def _watchdog(self, pid: int, run_id: int, timeout: int = 1800): """Kill agent if it exceeds timeout.""" import time, signal time.sleep(timeout) try: os.kill(pid, signal.SIGKILL) logger.warning(f"Agent run_id={run_id} killed after {timeout}s timeout") conn = get_db() conn.execute( "UPDATE agent_runs SET finished_at=datetime('now'), exit_code=-9 WHERE id=?", (run_id,) ) conn.commit() conn.close() except ProcessLookupError: pass # Already finished # В launch(), после Popen: t = threading.Thread(target=self._watchdog, args=(proc.pid, run_id), daemon=True) t.start() ``` **Проверка:** Запустить агента с заведомо долгой задачей → через 30 мин процесс убит, в БД exit_code=-9. --- ## Порядок выполнения 1. Dockerfile (git) → rebuild 2. launcher.py (git env + task_id + timeout) 3. plane.py (task_id в вызове launch) 4. gitea.py (auto-advance после CI green) 5. Rebuild + restart + smoke test ## Файлы для изменения - `/home/slin/repos/orchestrator/Dockerfile` - `/home/slin/repos/orchestrator/src/agents/launcher.py` - `/home/slin/repos/orchestrator/src/webhooks/plane.py` - `/home/slin/repos/orchestrator/src/webhooks/gitea.py` ## Ограничения - НЕ менять порт 8500 - НЕ менять формат .env - НЕ менять структуру БД (только использовать существующие колонки) - НЕ добавлять новые зависимости (threading уже в stdlib) - Тесты: `cd /home/slin/repos/orchestrator && python -m pytest tests/ -v` ## Команды проверки после деплоя ```bash # 1. Git в контейнере docker exec orchestrator git --version # 2. Health curl -s http://localhost:8500/health # 3. Тесты cd /home/slin/repos/orchestrator && python -m pytest tests/ -v # 4. Smoke: запустить агента и проверить task_id docker exec orchestrator python -c " from src.agents.launcher import launcher run_id = launcher.launch('architect', 'enduro-trails', 'test', task_id=99) print(f'run_id={run_id}') import sqlite3 conn = sqlite3.connect('/app/data/orchestrator.db') conn.row_factory = sqlite3.Row r = conn.execute('SELECT * FROM agent_runs WHERE id=?', (run_id,)).fetchone() print(dict(r)) " ```