Files
jellybit/openspec/changes/archive/2026-06-30-download-failure-recovery/proposal.md
T
avandClaude Opus 4.8 70d8758646 Восстановление зависших загрузок и уведомления о падении (state-reconciliation)
Долгий metaDL больше не убивается агрессивным таймаутом: дефолт
magnet_timeout 30m → 24h (страховочный предохранитель), базис отсчёта —
added_on из qBittorrent, а не created_at (переживает retry/усыновление).

Авто-восстановление: фоновая сверка возвращает в поток задачи, упавшие по
нашей нетерпеливости (magnet_timeout/stalled), когда источник ожил и
продвинулся за условие падения (downloading/completed по статусу торрента);
qbit_error не воскрешается. Конфликт idempotency (infohash занят другой
активной задачей) — оставляем в failed.

Уведомления: любой переход в failed/stuck пингует автора (включая приёмный
qbit_add через ingest), с дебаунсом против спама при флаппинге stalled.
Ручной retry добавлен в веб-UI и Telegram; Retry перецепляется к живому
торренту вместо слепого Add.

Дельта state-reconciliation влита в живые спеки; обновлён workflow.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:51:57 +03:00

5.9 KiB
Raw Blame History

Why

Загрузка magnet'ом ушла в терминальный failed/magnet_timeout по wall-clock таймауту (возраст от created_at, ~1ч), хотя qBittorrent просто долго тянул метаданные (медленные трекеры / мало пиров). Метаданные в итоге пришли, торрент жив и качается, но задача застряла в терминальном состоянии без выхода — восстановить её нельзя. Вдобавок падение происходит молча (нет уведомления автору), а единственный путь возврата Worker.Retry баговый (не сбрасывает базис времени → задача мгновенно снова падает) и доступен только через REST, но не из веб-UI и Telegram.

What Changes

  • Терпеливость к metaDL. Перестаём убивать долгий magnet агрессивным таймаутом. Дефолт [worker].magnet_timeout поднимается до 24h — это редкий страховочный предохранитель, а не рабочий механизм. Настоящие провалы определяются по статусам ошибок qBittorrent (error/missingFilesqbit_error), а не по wall-clock. (У qBittorrent нет статуса «magnet мёртв» — зависший magnet вечно висит в metaDL, поэтому единственный сигнал на этот кейс — большой страховочный таймаут.)
  • Базис таймаута — от факта, а не от created_at. Возраст для magnet_timeout/stalled считаем от времени добавления торрента в qBittorrent (added_on), а не от создания записи. Это чинит неверный отсчёт для усыновлённых раздач и устраняет мгновенное повторное падение после возврата в downloading.
  • Уведомление о любом падении. Переход в failed (любой error_code) и stuck уведомляет автора загрузки через notifier (раньше уведомления слались только для review/done/orphaned/target_missing).
  • Авто-восстановление из failed/stuck. Фоновая сверка (state-reconciliation) замечает, что у задачи в восстановимом failed/stuck (наша нетерпеливость: magnet_timeout, stalled) источник в qBittorrent жив и продвинулся, и возвращает задачу в поток (downloading либо completed по статусу торрента). Пользовательские и реальные провалы (qbit_error, reverted, cancelled) сверка не воскрешает.
  • Ручной retry из UI и Telegram. Кнопка повторной попытки добавляется в веб-UI и Telegram-бот (раньше — только Cancel; retry был только в REST). Worker.Retry чинится: перецепляется к уже живому торренту вместо слепого повторного Add, базис таймаута сбрасывается.

Capabilities

New Capabilities

Нет. Уведомления о падении и семантика таймаута относятся к жизненному циклу загрузки, который пока живёт в docs/specs/workflow.md (ещё не мигрирован в OpenSpec); заводить отдельную capability notifications сейчас — преждевременное дробление.

Modified Capabilities

  • state-reconciliation: восстановимые failed/stuck (magnet_timeout, stalled) перестают быть «неприкосновенными» для сверки и подлежат авто-восстановлению при живом продвинувшемся источнике; добавляется требование о восстановлении и о доступности ручного retry. qbit_error, reverted, cancelled, deleted остаются вне восстановления.

Impact

  • Спеки: дельта state-reconciliation; обновление графа переходов и семантики таймаута/уведомлений в docs/specs/workflow.md (источник истины по жизненному циклу до миграции).
  • Конфиг: дефолт [worker].magnet_timeout24h (internal/config/config.go), config.example.toml.
  • Код: internal/worker/worker.gotransition (уведомление о failed/stuck), checkTimeouts (базис от added_on), reconcile/ reconcileDesync (воскрешение из failed/stuck), Retry (перецепление + сброс базиса); новый Event падения и его обработка в notifier/ tgbot/httpapi; кнопка retry в internal/httpapi и internal/tgbot; вынос строки "magnet_timeout" (и смежных error_code) в именованные константы рядом с состояниями.
  • qBittorrent-клиент: возможно потребуется поле added_on в qbt.Torrent (если ещё не читается).
  • Миграции БД: не ожидаются (восстановление опирается на состояние qBit и существующие поля задачи).