From 447d878e0e9b37f563f9a96cd9d2539c9108d07b Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 15 Oct 2026 03:29:23 +0400 Subject: [PATCH] =?UTF-8?q?fix(hub):=20Python=20=D0=BE=D1=82=20uv=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20miniconda=20=E2=80=94=20SQLite?= =?UTF-8?q?=203.51.1=20=D0=B2=D0=B5=D1=88=D0=B0=D0=BB=20=D1=85=D0=B0=D0=B1?= =?UTF-8?q?=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BD=D0=BE=D0=B9=20=D0=B1?= =?UTF-8?q?=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=BE=D0=B9=20?= =?UTF-8?q?(O-14,=20R-11,=20#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стеки зависшего тестового хаба: потоки запросов ждут мьютексы unix-VFS SQLite в sqlite3.connect (findReusableFd) и close (sqlite3WalClose -> unixLock). Ошибка SQLite 3.51.0–3.51.1, исправлена в 3.51.2. .python-version = 3.13 и tool.uv.python-preference = only-managed: uv берёт свой CPython 3.13.12 (SQLite 3.50.4) и сам пересоздаёт .venv. Тест не даёт вернуться на SQLite 3.51.0–3.51.1. Co-Authored-By: Claude Opus 5.5 --- .python-version | 1 + CLAUDE.md | 2 +- README.md | 4 ++++ openspec/changes/web-app-followups/design.md | 12 ++++++++++++ pyproject.toml | 5 +++++ tests/test_db.py | 6 ++++++ 6 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/CLAUDE.md b/CLAUDE.md index 16ef7bd..fda9888 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ ## Стек и ограничения - Веб-приложение — `web/` (React 19 + TypeScript + Vite, TanStack Query, Vitest, Playwright; Node 24, npm); сборка в `src/jira_hub/web/app/` не в git. -- Python 3.13, uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). +- Python 3.13 от uv (`.python-version`, `tool.uv.python-preference = "only-managed"`: системный Python miniconda несёт SQLite 3.51.1 с взаимной блокировкой, O-14 #29), uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). - Хаб не знает пароль Jira и не ходит в Jira: учётные данные и сеть до Jira только у агента. - Зеркало задач пишет только `mirror.apply_state` по загрузке состояния от агента (`POST /agent/state`); правки идут командами через очередь. - Хаб не создаёт команды сам: повтор после failed — новая команда по запросу человека/интерфейса. Команду, выданную агенту и оставшуюся без отчёта, хаб довыдаёт при следующем claim (до 3 выдач, затем failed). diff --git a/README.md b/README.md index 3bf6e97..7e38464 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ HTTP API в локальной сети и MCP-сервер для Claude. Ха uv sync ``` +Python для хаба uv ставит свой (3.13, `.python-version`; системный и conda-Python не берутся — `tool.uv` в +`pyproject.toml`): в SQLite 3.51.0–3.51.1 есть взаимная блокировка, от которой хаб зависал под параллельными запросами. +Первый `uv sync` или `uv run` после смены сам пересоздаёт `.venv`. + Данные лежат в `~/.local/share/jira-hub/` (каталог переопределяется переменной `JIRA_HUB_HOME`): `hub.sqlite` и `backups/`. Пакет `mcp` закреплён версией `<2` (рулинг R-10): MCP SDK 2.x ломает API. diff --git a/openspec/changes/web-app-followups/design.md b/openspec/changes/web-app-followups/design.md index 7703cf3..94c8d3a 100644 --- a/openspec/changes/web-app-followups/design.md +++ b/openspec/changes/web-app-followups/design.md @@ -158,3 +158,15 @@ jira-hub#29 (46 пунктов, маркеры сохраняются из PR #2 клик по видимой карточке без ожидания кадров. Обоснование: воспроизвести потерю прокрутки у пользователя, кликающего по видимой карточке, не удалось ни в e2e, ни вручную. Цена ошибки: если дефект есть у реальных пользователей (например, в браузере с иной раскладкой событий scroll), прокрутка после возврата будет 0; чинится сохранением в cleanup `useLayoutEffect` (до снятия DOM). +- **R-11, 15.10.2026.** O-14 (зависание тестового хаба в e2e) — не дефект приложения, а взаимная блокировка в unix-VFS + SQLite 3.51.1 (исправлена в 3.51.2): стеки снятого зависшего хаба — все потоки запросов в `sqlite3.connect` + (`findReusableFd`) и один в `close` (`sqlite3WalClose` → `unixLock`), ждут мьютексы друг друга. SQLite 3.51.1 — у Python + miniconda, на котором стоял `.venv` (и у живого хаба). Хаб переведён на Python 3.13 от uv (python-build-standalone, + SQLite 3.50.4): `.python-version` = 3.13, `[tool.uv] python-preference = "only-managed"`; тест + `test_sqlite_without_unix_vfs_deadlock` не даёт вернуться на 3.51.0–3.51.1. Проверка: на старом окружении хаб завис на + 3-м и 5-м прогонах e2e, на новом — 20 прогонов подряд без зависаний (и 20 — на Python 3.14 с SQLite 3.53.4). Python 3.14 + не взят: в нём `Path.is_dir()` при отказе доступа возвращает False вместо исключения, и реле пишет «папки нет» вместо + «нет доступа» (падает `test_permission_denied_warns_once_then_works`). Обоснование: правка в границах #29, потому что + без неё не выполняется условие I-1 (10 прогонов e2e подряд); код хаба не меняется. Цена ошибки: живой хаб при первом + запуске после merge сам пересоздаёт `.venv` на Python от uv (нужен интернет для загрузки, ~1 мин); если что-то пойдёт + не так — убрать `[tool.uv]` и `.python-version`, `uv sync`, перезапуск. diff --git a/pyproject.toml b/pyproject.toml index ac01160..260b499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,3 +20,8 @@ packages = ["src/jira_hub"] [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.uv] +# Python от uv (python-build-standalone): SQLite в нём своя. В SQLite 3.51.0–3.51.1 взаимная блокировка в unix-VFS +# вешает хаб под параллельными запросами (O-14, #29); у miniconda — 3.51.1 +python-preference = "only-managed" diff --git a/tests/test_db.py b/tests/test_db.py index 8fd7025..28f692b 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -194,3 +194,9 @@ def test_migrated_claimed_redelivered_end_to_end(tmp_path): assert cmd["id"] == 1 assert queue.get_command(conn, 1)["deliveries"] == 2 conn.close() + + +def test_sqlite_without_unix_vfs_deadlock(): + # O-14 (#29): в SQLite 3.51.0–3.51.1 взаимная блокировка в unix-VFS (исправлена в 3.51.2) вешала хаб под + # параллельными запросами — потоки ждали друг друга в sqlite3.connect/close. Python хаба — от uv (pyproject, tool.uv) + assert sqlite3.sqlite_version_info[:2] != (3, 51) or sqlite3.sqlite_version_info >= (3, 51, 2), sqlite3.sqlite_version