Files
wiki/tasks/orchestrator/DEV_TASK_CI_FAIL_RETRY.md
2026-06-04 01:40:01 +03:00

8.1 KiB
Raw Blame History

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 (счётчик). Маленькая правка — не раздувай. Один исполнитель.