From 9d9471009704df84f4fedeb489308d87abe072c0 Mon Sep 17 00:00:00 2001 From: vanthinh6886 Date: Mon, 11 May 2026 21:22:23 +0700 Subject: [PATCH] fix: guard fcntl.flock(LOCK_UN) against OSError in finally blocks When fcntl.flock(fd, LOCK_UN) raises OSError in a finally block, the subsequent cleanup code (e.g., fd.close()) is skipped, causing the file descriptor to leak and the lock to be held indefinitely. This pattern existed in 5 files: - agent/shell_hooks.py: _allowlist_editor() - tools/skill_usage.py: _file_lock() - tools/memory_tool.py: _file_lock() - tools/environments/file_sync.py: sync_back() - hermes_cli/auth.py: _credential_file_lock() The Windows msvcrt path was already guarded with try/except in all these files, but the POSIX fcntl path was not. Fix: wrap each fcntl.flock(LOCK_UN) call in try/except OSError: pass, matching the defensive pattern already used for the msvcrt path. Fixes #21719 --- agent/shell_hooks.py | 5 ++++- hermes_cli/auth.py | 5 ++++- tools/environments/file_sync.py | 5 ++++- tools/memory_tool.py | 5 ++++- tools/skill_usage.py | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/agent/shell_hooks.py b/agent/shell_hooks.py index d45851fea6cee..32e3a8d72ddbf 100644 --- a/agent/shell_hooks.py +++ b/agent/shell_hooks.py @@ -624,7 +624,10 @@ def _locked_update_approvals() -> Iterator[Dict[str, Any]]: yield data save_allowlist(data) finally: - fcntl.flock(lock_fh.fileno(), fcntl.LOCK_UN) + try: + fcntl.flock(lock_fh.fileno(), fcntl.LOCK_UN) + except OSError: + pass def _prompt_and_record( diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 42e2f720874b5..d1fd7a19bf1d3 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -914,7 +914,10 @@ def _file_lock( finally: holder.depth = 0 if fcntl: - fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) + try: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) + except OSError: + pass elif msvcrt: try: lock_file.seek(0) diff --git a/tools/environments/file_sync.py b/tools/environments/file_sync.py index b778be87eb8aa..5539ffef048ae 100644 --- a/tools/environments/file_sync.py +++ b/tools/environments/file_sync.py @@ -289,7 +289,10 @@ def _sync_back_locked(self, lock_path: Path) -> None: fcntl.flock(lock_fd, fcntl.LOCK_EX) self._sync_back_impl() finally: - fcntl.flock(lock_fd, fcntl.LOCK_UN) + try: + fcntl.flock(lock_fd, fcntl.LOCK_UN) + except OSError: + pass lock_fd.close() def _sync_back_impl(self) -> None: diff --git a/tools/memory_tool.py b/tools/memory_tool.py index 80ee3c63d67ee..e74722a2bc4cd 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -169,7 +169,10 @@ def _file_lock(path: Path): yield finally: if fcntl: - fcntl.flock(fd, fcntl.LOCK_UN) + try: + fcntl.flock(fd, fcntl.LOCK_UN) + except OSError: + pass elif msvcrt: try: fd.seek(0) diff --git a/tools/skill_usage.py b/tools/skill_usage.py index e25f1365446a3..2b4e9a44782d3 100644 --- a/tools/skill_usage.py +++ b/tools/skill_usage.py @@ -86,7 +86,10 @@ def _usage_file_lock(): yield finally: if fcntl: - fcntl.flock(fd, fcntl.LOCK_UN) + try: + fcntl.flock(fd, fcntl.LOCK_UN) + except OSError: + pass elif msvcrt: try: fd.seek(0)