From 3a4fed94ff80c5d4002052a34dfe724002ea2346 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 14 Oct 2026 22:35:33 +0400 Subject: [PATCH] =?UTF-8?q?fix(hub):=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0=20=D0=BA=20?= =?UTF-8?q?release/=20=D0=BD=D0=B5=20=D1=80=D0=BE=D0=BD=D1=8F=D0=B5=D1=82?= =?UTF-8?q?=20claim=20=E2=80=94=20=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B5=D1=82=20(I-1,=20#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- src/jira_hub/api.py | 6 +++++- src/jira_hub/release.py | 6 +++--- tests/test_api.py | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/jira_hub/api.py b/src/jira_hub/api.py index bc70509..ac4729c 100644 --- a/src/jira_hub/api.py +++ b/src/jira_hub/api.py @@ -113,7 +113,11 @@ def h_claim(hub, conn, m, q, body): if cmd or time.monotonic() >= deadline: break time.sleep(1) - rel = release.current(hub.release_dir) + try: # команда уже выдана: публикация не должна превращать ответ в 500 (I-1) + rel = release.current(hub.release_dir) + except Exception: # noqa: BLE001 + log.exception("release: чтение публикации при claim") + rel = None return 200, {"command": cmd, "settings_version": settings.get(conn, "settings_version"), "agent_release": {"version": rel["version"], "sha256": rel["sha256"]} if rel else None} diff --git a/src/jira_hub/release.py b/src/jira_hub/release.py index 8ce1569..a0d093a 100644 --- a/src/jira_hub/release.py +++ b/src/jira_hub/release.py @@ -62,12 +62,12 @@ def publish(release_dir, source) -> dict: def current(release_dir) -> dict | None: """Опубликованная версия по манифесту; нет или битый манифест — None (агенту нечего обновлять).""" path = Path(release_dir) / "manifest.json" - if not path.exists(): - return None try: m = json.loads(path.read_text(encoding="utf-8")) return {"version": int(m["version"]), "sha256": str(m["sha256"]), "published_at": m.get("published_at")} - except (OSError, ValueError, KeyError, TypeError) as e: + except (FileNotFoundError, NotADirectoryError): + return None + except (OSError, ValueError, KeyError, TypeError) as e: # нет прав, битый JSON: публикации нет, мост работает log.warning("release: манифест %s не читается (%s) — публикации нет", path, e) return None diff --git a/tests/test_api.py b/tests/test_api.py index 5459c72..13c6a08 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,4 +1,5 @@ import json +import os import threading import urllib.error import urllib.request @@ -462,3 +463,22 @@ def test_hint_without_launcher(hub, tmp_path): def test_agent_view_without_release(hub): v = hub["client"].get("/agent") assert v["release"] is None and v["update_state"] is None and v["launcher_version"] is None and v["update_hint"] is None + + +def test_claim_survives_unreadable_release(hub): + rd = hub["hub"].release_dir + rd.mkdir() + (rd / "manifest.json").write_text('{"version": 6, "sha256": "x"}', encoding="utf-8") + os.chmod(rd, 0) + try: + b = hub["client"].post("/batches", {"title": "т", "author": "claude", + "commands": [{"type": "ensure_fields", "params": {"key": "A-1", "duedate": "2026-10-23"}}]}) + r = hub["agent"].post("/agent/claim", {"agent_version": 5}) + assert r["command"] is not None and r["agent_release"] is None # публикация не роняет выдачу команды + assert hub["client"].get("/agent")["release"] is None + with pytest.raises(HubError) as e: + hub["agent"].get("/agent/release") + assert e.value.status == 404 + assert b["id"] + finally: + os.chmod(rd, 0o700)