fix(tools): forgejo fetch timeout + retries #40
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Контекст
create-issuetool висит ~5 минут после инвокации. Исследование показало:.opencode/tools/_shared.ts:86—fetch()к Forgejo API вызывается безsignal/AbortController/таймаута. Рантайм — Bun 1.2 (opencode — Bun-compiled binary),Bun.fetch()не имеет дефолтного таймаута (бесконечный).await u.execute(...).FORGEJO_URL = https://git.slaid098.devза Nginx. Forgejo использует SQLite (write-lock contention). 5 минут — серверсайд-таймаут, пока fetch покорно ждёт..opencode/tools/_shared.ts:34—spawnSync("git", ...)безtimeout— тоже беззащитен.context.abort(SDKToolContext.abort: AbortSignal) существует, но нигде не используется — пользовательская отмена tool-call из UI не работает.Симптом: агент вызывает
create-issue, инструмент уже выполняется, но висит ~5 минут, issue не создаётся.Задача
callForgejo(_shared.ts:86) обернутьfetch()в retry-цикл с таймаутом черезAbortSignal.any([userSignal, AbortSignal.timeout(timeoutMs)]).resolveForgejoRepo(_shared.ts:34) добавитьtimeout: 5000кspawnSync.context.abortиз всех tool'ов (create-issue.ts,create-pr.ts,merge-pr.ts) черезrunGh→callForgejoGh→callForgejo.OPENCODE_FORGEJO_TIMEOUT(10с),OPENCODE_FORGEJO_RETRY(3),OPENCODE_FORGEJO_RETRY_INTERVAL(1с).process.stderr.writeс префиксом[forgejo](конвенция репо, NOTconsole.*)..env.example.Контракты
callForgejo(method, path, opts, userSignal?)— добавляется 4-й опциональный параметрuserSignal?: AbortSignal.callForgejoGh(args, repo, opts, signal?)— добавляетсяsignal?: AbortSignal.runGh(args, repo, opts, signal?)— добавляетсяsignal?: AbortSignal.AbortError(timeout) и HTTP 5xx; НЕ на 4xx (клиентская ошибка — повтор не поможет).pipeline-status.py:596).OPENCODE_FORGEJO_TIMEOUT=10,OPENCODE_FORGEJO_RETRY=3,OPENCODE_FORGEJO_RETRY_INTERVAL=1. Худший кейс:10×3 + 2×1 = 32с.Инварианты
callForgejo/callForgejoGh/runGhбезsignalпродолжают работать (параметр опциональный).context.abortотменяет fetch немедленно, без ретрая.resolveForgejoRepoпри timeoutspawnSyncвозвращает читаемую ошибку, не hang.Граничные случаи
AbortSignal.anyсуществует в Bun 1.2 (подтверждено через Context7 — встроенный Web API, C++ реализация вAbortSignal.cpp).AbortSignal.timeout(0)— edge case, должен сразу фейлиться.resolveLabelIdsделает доп. fetch'и (GET /labels,POST /labels) — должны тоже идти через retry-логику (используют тот жеcallForgejo).OPENCODE_FORGEJO_RETRY=0— мгновенный фейл без retry (валидный edge case).OPENCODE_FORGEJO_TIMEOUT=0— невалидный, fallback на дефолт (логировать warning).Влияние на связанные компоненты
create-issue.ts,create-pr.ts,merge-pr.ts— пробросcontext.abort(1 строка на каждый)..env.example— 3 новые переменные.context.abortначинает использоваться впервые в репо — задел для других tool'ов.pipeline-status.py(там своя retry-логика, конвенция совпадает — берём как референс).Вне scope
PRAGMA busy_timeout, webhook sync/async, search indexer, Nginxproxy_read_timeout) — отдельный issue вforgejo-infraпосле merge этого.opencode.json— opencode core, не этот репо.pipeline-status.py).Критерии приемки
callForgejoиспользуетAbortSignal.any([userSignal, AbortSignal.timeout(timeoutMs)]).callForgejoретраит на timeout и 5xx, НЕ ретраит на 4xx.resolveForgejoRepospawnSyncимеетtimeout: 5000.create-issue.ts,create-pr.ts,merge-pr.tsпробрасываютcontext.abort.OPENCODE_FORGEJO_TIMEOUT/RETRY/RETRY_INTERVALработают с дефолтами 10/3/1.process.stderr.writeс[forgejo]prefix, успешные вызовы не логируются._shared.test.ts, Bun test): timeout triggers retry, 5xx triggers retry, 4xx no-retry, retries-exhausted readable error, user-abort no-retry,resolveLabelIdsrespects timeout,resolveForgejoRepospawnSync timeout..env.exampleзадокументирован.signalпродолжают работать (backward compatible).