Each copy re-implemented temp+replace by hand and lacked one or more of fsync, symlink preservation, atomic_replace's Windows-contention retry and EXDEV/bind-mount fallback, mode preservation, or interrupt-safe temp cleanup. Three (gateway/session_persistence, cron/suggestions, agent/shell_hooks) were verbatim inlines of utils._atomic_write; two modules defined their own directory-fsync helper, now utils.fsync_directory. plugins/google_meet/_jsonfile.write_json_atomic is deleted (callers use the canonical helper directly). Behavior change: every one of these writers now fsyncs the payload, keeps a pre-existing target's mode, cleans its temp file on BaseException, and survives Windows AV/indexer contention and cross-device renames the way config writes already did. cron/suggestions.json is 0600 from creation (previously chmod'ed after the replace). Skipped on purpose: cron/jobs.py two-phase staging, gateway/status._write_json_excl (create-only lock), kanban_transfer staging (not atomic writers); tools/skill_usage. _write_suppressed_names lives inside a PLUGIN-COMPAT block.
18 lines
496 B
Python
18 lines
496 B
Python
"""Tiny JSON read helper shared by the bot, process manager, node registry and node server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
|
|
def read_json(path: Path) -> Optional[Any]:
|
|
"""Parsed JSON from *path*, or None when missing/unreadable/malformed."""
|
|
if not path.is_file():
|
|
return None
|
|
try:
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, ValueError):
|
|
return None
|