diff --git a/agent/copilot_acp_client.py b/agent/copilot_acp_client.py index f1bff1a7190..b24ddbef5da 100644 --- a/agent/copilot_acp_client.py +++ b/agent/copilot_acp_client.py @@ -636,7 +636,10 @@ class CopilotACPClient: block_error = get_read_block_error(str(path)) if block_error: raise PermissionError(block_error) - content = path.read_text() if path.exists() else "" + try: + content = path.read_text() + except FileNotFoundError: + content = "" line = params.get("line") limit = params.get("limit") if isinstance(line, int) and line > 1: diff --git a/agent/shell_hooks.py b/agent/shell_hooks.py index 79d494d7dcb..4e2b2ddd7c3 100644 --- a/agent/shell_hooks.py +++ b/agent/shell_hooks.py @@ -632,7 +632,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, IOError): + pass def _prompt_and_record( diff --git a/cron/scheduler.py b/cron/scheduler.py index ebfc2b153b0..6302227e8f6 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -1950,7 +1950,10 @@ def tick(verbose: bool = True, adapters=None, loop=None) -> int: return sum(_results) finally: if fcntl: - fcntl.flock(lock_fd, fcntl.LOCK_UN) + try: + fcntl.flock(lock_fd, fcntl.LOCK_UN) + except (OSError, IOError): + pass elif msvcrt: try: msvcrt.locking(lock_fd.fileno(), msvcrt.LK_UNLCK, 1) diff --git a/gateway/sticker_cache.py b/gateway/sticker_cache.py index f3b874019f4..c5368173067 100644 --- a/gateway/sticker_cache.py +++ b/gateway/sticker_cache.py @@ -9,6 +9,8 @@ Cache location: ~/.hermes/sticker_cache.json """ import json +import os +import tempfile import time from typing import Optional @@ -35,12 +37,23 @@ def _load_cache() -> dict: def _save_cache(cache: dict) -> None: - """Save the sticker cache to disk.""" + """Save the sticker cache to disk atomically.""" CACHE_PATH.parent.mkdir(parents=True, exist_ok=True) - CACHE_PATH.write_text( - json.dumps(cache, indent=2, ensure_ascii=False), - encoding="utf-8", + fd, tmp_path = tempfile.mkstemp( + dir=str(CACHE_PATH.parent), suffix=".tmp" ) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + json.dump(cache, f, indent=2, ensure_ascii=False) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp_path, str(CACHE_PATH)) + except BaseException: + try: + os.unlink(tmp_path) + except OSError: + pass + raise def get_cached_description(file_unique_id: str) -> Optional[dict]: diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 0848a3d2c4b..47ac0e12bb5 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -954,7 +954,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, IOError): + pass elif msvcrt: try: lock_file.seek(0) diff --git a/tools/environments/file_sync.py b/tools/environments/file_sync.py index b778be87eb8..6de78c87b84 100644 --- a/tools/environments/file_sync.py +++ b/tools/environments/file_sync.py @@ -289,7 +289,10 @@ class FileSyncManager: 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, IOError): + pass lock_fd.close() def _sync_back_impl(self) -> None: diff --git a/tools/memory_tool.py b/tools/memory_tool.py index 42737f66c4f..78d3a154933 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -166,7 +166,10 @@ class MemoryStore: yield finally: if fcntl: - fcntl.flock(fd, fcntl.LOCK_UN) + try: + fcntl.flock(fd, fcntl.LOCK_UN) + except (OSError, IOError): + pass elif msvcrt: try: fd.seek(0) diff --git a/tools/skill_usage.py b/tools/skill_usage.py index e25f1365446..6bffb86d1d6 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, IOError): + pass elif msvcrt: try: fd.seek(0) diff --git a/trajectory_compressor.py b/trajectory_compressor.py index fcf699d1fdc..7ef396daa8b 100644 --- a/trajectory_compressor.py +++ b/trajectory_compressor.py @@ -126,10 +126,10 @@ class CompressionConfig: def from_yaml(cls, yaml_path: str) -> "CompressionConfig": """Load configuration from YAML file.""" with open(yaml_path, 'r', encoding="utf-8") as f: - data = yaml.safe_load(f) - + data = yaml.safe_load(f) or {} + config = cls() - + # Tokenizer if 'tokenizer' in data: config.tokenizer_name = data['tokenizer'].get('name', config.tokenizer_name)