fix(merge_gate): retry transient Gitea merge errors + already-in-main guard
merge_pr now wraps ONLY the mutating POST /pulls/{n}/merge in a bounded
exponential-backoff retry-loop on TRANSIENT outcomes (405 "try again later",
408, any 5xx, network/timeout, and 409|422 while the PR is still mergeable);
TERMINAL outcomes (403/404/real conflict via mergeable==False) -> fast honest
False, so the ORCH-071/081 not-merged HOLD backstop is unchanged. Fixes the
ORCH-063 false HOLD + manual re-merge on Gitea's post-push mergeability hiccup.
ensure_open_pr gains an "already fully in main" guard (_branch_fully_in_main,
git merge-base --is-ancestor HEAD origin/main) BEFORE creating a PR -> new
"already-in-main" outcome avoids the garbage empty PR on a re-driven finalizer;
_handle_merge_verify skips merge_pr on that outcome and lets the authoritative
SHA-in-main check confirm -> done (not a HOLD). git error of the guard fails
OPEN to the create path.
New ORCH_MERGE_RETRY_* settings (kill-switch merge_retry_enabled -> one-shot,
max_attempts=3, backoff base=2/max=5). INV-4 (merge only via Gitea PR-merge API,
never push/force-push main), never-raise, STAGE_TRANSITIONS/QG_CHECKS/DB schema
unchanged. Docs (README merge-verify section, CLAUDE.md, CHANGELOG, .env.example)
updated in the same PR. Tests: test_merge_gate.py TC-01..12, test_config.py
TC-13, test_merge_verify.py TC-14..16; full suite green (1389).
Refs: ORCH-093
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -477,6 +477,44 @@ developer-пути и **только** при свежем worktree-коммит
|
||||
Подробнее: [adr-0016](adr/adr-0016-ensure-open-pr-before-merge-verify.md) (amends 0013/0014);
|
||||
детально — `docs/work-items/ORCH-082/06-adr/ADR-001-ensure-open-pr-before-merge-verify.md`.
|
||||
|
||||
#### Ретрай транзиентных merge-ошибок Gitea + гард already-in-main (ORCH-093 — фикс ложного HOLD на 405/5xx)
|
||||
Инцидент **ORCH-063**: self-deploy прошёл, staging OK, PR был `open`+`mergeable`, конфликтов не было,
|
||||
но `POST /pulls/{n}/merge` вернул `HTTP 405 {"message":"Please try again later"}` (Gitea пересчитывал
|
||||
`mergeable` сразу после пуша). One-shot `merge_pr` мгновенно вернул `False` → корректная защита
|
||||
ORCH-071/073 удержала задачу на `deploy` (HOLD+alert) + потребовала **ручной домерж** (повтор влился с
|
||||
первого раза); повторный прогон финализатора плодил **мусорный пустой PR** на уже влитой ветке. У
|
||||
Claude-агентов есть transient-breaker, у CI-гейта — `check_ci_green`, а у детерминированного
|
||||
merge-актора аналога не было. ORCH-093 закрывает это аддитивно, внутри того же под-гейта, не трогая
|
||||
машину стадий:
|
||||
- **Retry-loop в `merge_pr` (ORCH-093 D1/D2):** ретраится **только** мутирующий `POST …/merge`
|
||||
(идемпотентные шаги до него — без изменений). Классификатор `_classify_merge_response →
|
||||
transient|terminal`: **транзиент** (ретрай с backoff) — `405`/`408`/любой `5xx`/`httpx`-таймаут/
|
||||
сетевая ошибка, **и** `409`/`422` когда PR всё ещё `mergeable` (доп. `GET /pulls/{index}`);
|
||||
**терминал** (быстрый честный `False`, защита ORCH-071/073 как прежде) — `403`/`404`/реальный
|
||||
конфликт (`409`/`422` при `mergeable==False`). Дефолт-политика `mergeable==None`/недоступно →
|
||||
транзиент (fail-OPEN-в-ретрай: икота Gitea наблюдаема, бюджет конечен, backstop сохранён).
|
||||
Backoff экспоненциальный с потолком `min(base*2^(i-1), max)` (дефолты 2/5 с → суммарный сон
|
||||
`(N-1)*max ≤ 10 с`, monitor-поток merge-verify не подвешивается). Лог `attempt i/N` (образец
|
||||
`check_ci_green`).
|
||||
- **Гард already-in-main в `ensure_open_pr` (ORCH-093 D3):** leaf `_branch_fully_in_main`
|
||||
(`git merge-base --is-ancestor HEAD origin/main` в per-branch worktree) вызывается **между** «код-PR
|
||||
не найден» и `POST …/pulls`: ветка целиком в `main` (нет коммитов `origin/main..HEAD`) → новый исход
|
||||
`("already-in-main", …)` **без создания PR** (нет мусорного пустого PR). git-ошибка/ambiguous
|
||||
(`None`) → **fail-OPEN** (деградация на create-путь, НЕ ложный no-op). Без отдельного флага —
|
||||
накрыт `merge_verify_autocreate_pr_enabled`.
|
||||
- **Врезка в `_handle_merge_verify` (ORCH-093 D4):** `pr_status == "already-in-main"` → лог,
|
||||
**пропуск** `merge_pr` (мержить нечего), сразу к `verify_merged_to_main` (SHA-в-main подтвердит →
|
||||
`done`). Это НЕ HOLD; SHA-в-main остаётся авторитетным (если SHA не в `main` — прежний HOLD,
|
||||
fail-closed).
|
||||
- **Конфиг/откат:** `merge_retry_enabled` (kill-switch; `False` → ровно один POST = байт-в-байт
|
||||
прежнее one-shot) / `merge_retry_max_attempts` (3) / `merge_retry_backoff_base_s` (2) /
|
||||
`merge_retry_backoff_max_s` (5), env `ORCH_MERGE_RETRY_*`. `STAGE_TRANSITIONS`, `QG_CHECKS`, схема
|
||||
БД, exit-коды хука, merge-gate, image-freshness — без изменений; `main` не push/force-push.
|
||||
|
||||
Подробнее: [adr-0027](adr/adr-0027-merge-actor-transient-retry-and-already-in-main.md)
|
||||
(amends 0013/0014/0016); детально —
|
||||
`docs/work-items/ORCH-093/06-adr/ADR-001-merge-transient-retry-and-already-in-main-guard.md`.
|
||||
|
||||
#### Ретрай транзиентных merge-ошибок Gitea + гард already-in-main (ORCH-093 — фикс ложного HOLD на 405/5xx)
|
||||
Инцидент ORCH-063 (09.06): self-deploy прошёл, PR `open`+`mergeable=True`, конфликтов нет — но
|
||||
`POST …/merge` вернул `HTTP 405 {"message":"Please try again later"}` (Gitea пересчитывал
|
||||
|
||||
Reference in New Issue
Block a user