---
name: deployed-is-not-running
description: "«Файл на проде» ≠ «код обслуживает пользователей»; как доказывать, что процесс перечитал правки, и почему healthz/pytest/зелёный тайл этого не показывают"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 4eea915e-3b47-4cd5-8a0e-bfa29b78a87c
  modified: 2026-07-30T06:37:53.683Z
---

Перед тем как сказать «фикс задеплоен и работает», надо отдельно доказать, что **боевой
процесс перечитал файл**. Это два разных утверждения, и второе почти никогда не следует
из первого автоматически.

**Что случилось (Автеро, 2026-07-30):** правки легли на прод 29.07 в 23:35, но uvicorn
запущен **без `--reload`**, и процесс был от 26.07. Три дня фиксов, включая критическую
дыру захвата аккаунта через OAuth, не обслуживали пользователей. Причём все проведённые
проверки были честными: живые e2e шли через HTTP (проверяли старый код), а сами фиксы
проверялись прямыми вызовами модулей из боевого venv (проверяли новый код). Два верных
по отдельности измерения дали ложную уверенность.

**Не является доказательством:** `healthz` 200 · зелёный тайл ops_dashboard · `py_compile`
OK · зелёный pytest · наличие нужного текста в файле на проде · свежий `.pyc` (его пишет
сам `py_compile`, а в плисте может стоять `PYTHONDONTWRITEBYTECODE`).

**Является:**
1. `ps -eo pid,lstart` процесса против `stat` mtime изменённых файлов — самое дешёвое.
2. Поведенческий пробник, дающий на старом и новом коде РАЗНЫЙ ответ. В том случае это
   был `POST /api/feedback/distill` без бот-хедеров: старый код 200, новый 403.
   Такой пробник стоит придумывать **вместе с фиксом**, а не после.

**Как применять:** для любого сервиса под launchd (Автеро, ВакХантер, Делор, ТБР, zemlya,
КодИнвест, CryptoPilot, CebersPost) в конце доработки — либо `launchctl kickstart -k` +
пробник, либо явная сверка возраста процесса. Если сервис запущен без `--reload`, правка
файла НЕ доезжает до пользователя сама. Отдельно помнить: у Автеро часть переменных живёт
в **плисте**, а не в `.env` — прогон тестов с одним `.env` может врать (см.
[[env_source_defeats_test_setdefault]]).

**Повторно поймана в той же сессии, тем же днём (2026-07-30, #189).** `launchctl
kickstart` был issued ПОСЛЕ патча по порядку команд, но фактически процесс не поднялся —
живой e2e ещё раз показал старое поведение. Проверка `ps lstart` против `mtime` вскрыла:
процесс `09:12:01`, файл `09:28:08` — процесс СТАРШЕ патча, kickstart не сработал (или
сработал впустую). Вывод: «я вызвал kickstart после патча» — это утверждение о ПОРЯДКЕ
КОМАНД, а не о результате; kickstart может тихо не выполниться (гонка с launchd, файл
занят, сервис не найден по лейблу). После КАЖДОГО kickstart — обязательно сверять
свежий `ps lstart` нового pid против `mtime` файла, а не считать вызов успешным по коду
возврата 0.

Связано: [[investpilot_silent_outage_2026-07-27]] («зелёный тайл ≠ работает», тот же класс),
§7 CLAUDE.md (проверять реальным запуском), [[avtero_make_brand_matching_class]].
