8.1 KiB
DEV TASK — БАГ 7: красный CI на development должен авто-возвращать задачу developer'у (retry, как review)
Проект: orchestrator | Сервер: slin@82.22.50.71 | Репо: /home/slin/repos/orchestrator | Контейнер: orchestrator (8500)
Ветка: fix/ci-fail-retry-developer из свежего main (git checkout main && git pull && git checkout -b fix/ci-fail-retry-developer). main = 7922f6b (PR #17).
⚠️ ГРАБЛЯ push (ORCH-7): после push git log origin/main..origin/fix/ci-fail-retry-developer ДОЛЖЕН показать коммит ДО отчёта «PR готов». Токен Gitea: docker exec orchestrator printenv ORCH_GITEA_TOKEN.
Контекст / дыра
После бага 6 (PR #17) CI стал авторитетным гейтом стадии development. Но при красном CI наш код (gitea.py handle_ci_status, ветка elif state == "failure" and current_stage == "development":) делает ТОЛЬКО notify_qg_failure — уведомляет и встаёт. Никто не перезапускает developer'а → конвейер виснет, требует ручного вмешательства.
Для review такая ветка УЖЕ есть (request_changes → relaunch developer, max 3x). Нужно сделать симметрично для CI-failure.
Образец (УЖЕ в коде — копируй его логику) — handle_pr, ветка REQUEST_CHANGES, ~стр.289-314:
elif review_state == "REQUEST_CHANGES" and current_stage == "review":
conn = get_db()
retry_count = conn.execute(
"SELECT COUNT(*) as cnt FROM agent_runs WHERE task_id = ? AND agent = 'developer'",
(task_id,),
).fetchone()["cnt"]
conn.close()
if retry_count < MAX_DEV_RETRIES:
update_task_stage(task_id, "development")
notify_stage_change(task_id, current_stage, "development")
try:
task_desc = (... attempt {retry_count + 1}/{MAX_DEV_RETRIES} ...)
job_id = enqueue_job("developer", repo_name, task_desc, task_id=task_id)
logger.info(...)
except Exception as e:
notify_error(task_id, f"Failed to relaunch developer: {e}")
else:
notify_error(task_id, f"Max developer retries ({MAX_DEV_RETRIES}) reached, escalating")
logger.error(...)
MAX_DEV_RETRIES = 3 (gitea.py:33). Импорты enqueue_job, update_task_stage, notify_stage_change, notify_error, notify_qg_failure, get_db УЖЕ есть в файле — не дублируй.
Фикс — переписать CI-failure ветку в handle_ci_status (~стр.219-222)
БЫЛО:
elif state == "failure" and current_stage == "development":
# CI is now the authoritative gate for development -> review.
# A failing CI means the QG did not pass; notify (do not silently advance).
notify_qg_failure(task_id, current_stage, "check_ci_green", f"Gitea CI failed on branch '{branch}'")
СТАЛО (уведомить О ПРОВАЛЕ + авто-retry developer'а с тем же лимитом):
elif state == "failure" and current_stage == "development":
# CI is the authoritative gate for development -> review.
# On red CI: notify, then bounce the task back to the developer (capped retries),
# symmetric to the review REQUEST_CHANGES path.
notify_qg_failure(task_id, current_stage, "check_ci_green", f"Gitea CI failed on branch '{branch}'")
conn = get_db()
retry_count = conn.execute(
"SELECT COUNT(*) as cnt FROM agent_runs WHERE task_id = ? AND agent = 'developer'",
(task_id,),
).fetchone()["cnt"]
conn.close()
if retry_count < MAX_DEV_RETRIES:
# task already on 'development' — no stage change needed, just relaunch developer
try:
task_desc = (
f"Work item: {work_item_id}\nRepo: {repo_name}\nBranch: {branch}\n"
f"Stage: development\nNote: CI failed, fix and re-push (attempt {retry_count + 1}/{MAX_DEV_RETRIES})"
)
job_id = enqueue_job("developer", repo_name, task_desc, task_id=task_id)
logger.info(f"Task {task_id}: CI failed, enqueued developer (attempt {retry_count + 1}, job_id={job_id})")
except Exception as e:
notify_error(task_id, f"Failed to relaunch developer after CI failure: {e}")
else:
notify_error(task_id, f"Max developer retries ({MAX_DEV_RETRIES}) reached after CI failure, escalating")
logger.error(f"Task {task_id}: max retries reached after CI failure, needs manual intervention")
Ключевые отличия от review-ветки (учти!):
- Задача УЖЕ в
development→ НЕ зовиupdate_task_stage/notify_stage_change(в review был переход review→development, здесь перехода нет). - branch берётся из переменной
branch(неhead_branch). - Сохрани
notify_qg_failureПЕРЕД retry (чтобы Слава видел и провал, и факт ретрая).
Ограничения
- 🚫 НЕ трогай: review-retry ветку (образец оставь как есть), check_ci_green success-ветку, HMAC/project-filter, stages.py, qg/checks.py, stage_engine, nginx, openclaw.json, .env, deploy, очередь (enqueue_job не меняй), PLANE_STATES, conftest.py, status-only, gitea_public_url.
- 🚫 retry_count считается по ВСЕМ agent_runs developer'а задачи — это ОБЩИЙ лимит на developer (review-retry + CI-retry вместе ≤ MAX_DEV_RETRIES). Так и надо — общий потолок на доработки developer'а. НЕ заводи отдельный счётчик.
- Conventional Commits, один коммит:
fix(ci): bounce task back to developer on red CI (capped retries).
Тесты (контейнер)
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
- Добавь/обнови в tests/test_webhooks.py (рядом с
test_gitea_ci_failure_on_development_notifies_qg_failure):- CI failure на development при retry_count < 3 → enqueue_job("developer",...) ВЫЗВАН + notify_qg_failure вызван, stage остаётся "development".
- CI failure при retry_count >= 3 → enqueue_job НЕ вызван, notify_error («Max developer retries... after CI failure») вызван.
- (мокай get_db/agent_runs count и enqueue_job/notify_*). Эти тесты — pure-logic, НЕ через webhook-POST (чтобы не упереться в 401 HMAC-барьер baseline-9).
- Baseline после PR #17: 215 passed + 10 failed (9 baseline HMAC/401 + 1 webhook-POST тест на том же 401 — off-limits, не «чинить»). Итог passed ≥215 (+ твои новые pure-logic тесты). НЕ урони passed; новые webhook-тесты делай pure-logic (вызов handle_ci_status напрямую с моками), НЕ через TestClient POST.
Отчёт
- НЕ деплоить, НЕ мержить (мерж + боевой прогон делает ассистент: задеплою и дёрну CI-failure на ET-011 → должно вернуть developer'а, он починит 10× E402 в src/api/main.py и перезапушит → зелёный CI → development→review автономно).
- В отчёте: точный старый/новый код CI-failure ветки, новые тесты с пояснением, полный вывод pytest (счётчик). Маленькая правка — не раздувай. Один исполнитель.