From fd9e57703c1a381745456a6aee0ea14fd8e4d5a1 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:25:21 +0800 Subject: [PATCH] fix(trigger): tolerate unsupported directory fsync --- nanobot/triggers/local_store.py | 7 +++++- tests/triggers/test_local_triggers.py | 36 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/nanobot/triggers/local_store.py b/nanobot/triggers/local_store.py index 69ea5290..05250d53 100644 --- a/nanobot/triggers/local_store.py +++ b/nanobot/triggers/local_store.py @@ -2,6 +2,7 @@ from __future__ import annotations +import errno import json import os import secrets @@ -402,7 +403,11 @@ class LocalTriggerStore: with suppress(PermissionError): fd = os.open(str(path.parent), os.O_RDONLY) try: - os.fsync(fd) + try: + os.fsync(fd) + except OSError as exc: + if exc.errno != errno.EINVAL: + raise finally: os.close(fd) except BaseException: diff --git a/tests/triggers/test_local_triggers.py b/tests/triggers/test_local_triggers.py index f18ff1d9..8c818cf3 100644 --- a/tests/triggers/test_local_triggers.py +++ b/tests/triggers/test_local_triggers.py @@ -1,7 +1,9 @@ from __future__ import annotations import asyncio +import errno import json +import os from contextlib import suppress from pathlib import Path @@ -60,6 +62,40 @@ def test_trigger_store_allows_multiple_triggers_per_session(tmp_path: Path) -> N assert first.id != second.id +def test_trigger_store_atomic_write_ignores_unsupported_directory_fsync( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Shared folders may allow opening directories but reject directory fsync.""" + store = LocalTriggerStore(tmp_path) + real_open = os.open + real_fsync = os.fsync + directory_fds: set[int] = set() + + def fake_open(path: str, flags: int, *args: object, **kwargs: object) -> int: + fd = real_open(path, flags, *args, **kwargs) + if Path(path).name == "triggers": + directory_fds.add(fd) + return fd + + def fake_fsync(fd: int) -> None: + if fd in directory_fds: + raise OSError(errno.EINVAL, "Invalid argument") + real_fsync(fd) + + monkeypatch.setattr(os, "open", fake_open) + monkeypatch.setattr(os, "fsync", fake_fsync) + + trigger = store.create( + name="Shared folder safe", + channel="websocket", + chat_id="chat-1", + session_key="websocket:chat-1", + ) + + assert store.get(trigger.id) is not None + + def test_enqueue_rejects_disabled_trigger(tmp_path: Path) -> None: store = LocalTriggerStore(tmp_path) trigger = store.create(