---
name: handler-order-catchall-eats-commands
description: "aiogram: catch-all @router.message(F.text) съедает команды роутеров, подключённых позже — /integration молчала; проверять сверкой data-mid до и после"
metadata: 
  node_type: memory
  type: project
  originSessionId: d0c6c5aa-4d05-42e2-a1f9-034482b88603
  modified: 2026-08-05T12:45:16.228Z
---

**2026-08-01, бот «Земля без риска».** Команда `/integration` (реферальные материалы) не отвечала **вообще** — не ошибкой, а тишиной. Причина: `cadastral.router` подключён в `bot/main.py` раньше `integration_handler.router`, а в `cadastral.py` есть catch-all `@router.message(F.text)`, который ловит и команды и для нераспознанного текста делает `return`, а **не** `raise SkipHandler()`. Соседний `menu_intercept.py` в той же ситуации делает `SkipHandler` — то есть это ошибка, а не задумка.

**Правило:** любой роутер с командами подключать ДО `cadastral.router`. Новую кнопку панели тоже регистрировать в `start.py`, а не рядом с её логикой в `cadastral.py`, — иначе catch-all примет нажатие за кадастровый номер.

**Как ловить.** «Ответ не тот» и «ответа нет» — разные диагнозы, а на глаз неотличимы: тест читает последний пузырь и видит ответ на ПРЕДЫДУЩЕЕ сообщение. Сверять `data-mid` последнего входящего до и после отправки; не изменился — ответа не было. Я сначала записал в находки «/integration отдаёт текст помощи» — это была моя ошибка чтения.

**Того же класса, найдено тогда же:** `menu_intercept` матчил 2 кнопки из 7 — в наборе они записаны без пробела после эмодзи, а `KeyboardButton` его содержит; `_normalize` снимал только VS-16 и ZWJ. Совпадали лишь «ℹ️ Помощь» и «🛠️ Админка», где VS-16 случайно съедал разницу. Лечение — сравнивать без пробелов вовсе.

**Тот же класс, ТБР, 05.08.2026 — состояние FSM съедает команду.** В боте «Торги без риска» подсказка «Отмена — /start» стояла и в промокоде, и в обратной связи, но `@router.message(UserSG.feedback)` зарегистрирован ВЫШЕ `@router.message(Command("start"))` — фильтр состояния матчит любой текст, и `/start` уходил в него: сохранился как обращение №1 и улетел админам. Лечение — исключать команды из обработчика состояния (`~F.text.startswith("/")`), тогда сообщение проваливается дальше к `cmd_start`, который чистит состояние. **Обещание в тексте кнопки ничего не гарантирует — проверять отмену живым прогоном.**

См. [[zemlya_expert_report_rename_and_verify]].
