fix(profiles): sweep the remaining pre-write mkdirs under the deleted-profile guard

A long-lived serve process keeps a deleted profile as the context home of threads
that outlive the delete. A bare `mkdir(parents=True)` right before an atomic write
brings `profiles/<name>/` back after `hermes profile delete` has written the
tombstone and removed the tree.

The writers in `utils` and the seven callers named in #112592 are guarded by the
preceding commits; this one applies the same `mkdir_under_hermes_home` idiom to the
other pre-write directory creations found by the same mechanical rule (auth,
personality, plugin catalog, skills sync, tool discovery cache, platform adapters,
memory plugins, local runtime supervisor, process identity, breadcrumbs). The two
sites that pass `mode=` keep their mkdir behind `assert_named_profile_home_live`.
The guard is a no-op unless the target has a provable `profiles/<name>` ancestor.

Salvaged from #112596 (30-file sweep) on top of #112594 / #112601; the overlapping
files were resolved to the already-landed versions.
This commit is contained in:
Sora-bluesky
2026-09-16 13:26:22 +09:00
committed by Teknium
parent 5d97d5ed6d
commit 9085ef967c
21 changed files with 45 additions and 20 deletions
+2 -1
View File
@@ -149,7 +149,8 @@ def _write_config(cfg: dict, path: Path | None = None) -> None:
out = _apply_edits(cfg.snapshot, cfg, disk)
elif path.exists():
out = _apply_edits(cfg.snapshot, cfg, _overlay_local(cfg.snapshot, disk))
path.parent.mkdir(parents=True, exist_ok=True)
from hermes_constants import mkdir_under_hermes_home
mkdir_under_hermes_home(path.parent)
atomic_json_write(path, out, mode=0o600)
if isinstance(cfg, _ReadConfig): # a later write on the same object applies only edits made after this one
cfg.snapshot, cfg.path = copy.deepcopy(dict(cfg)), path
+2 -1
View File
@@ -2216,7 +2216,8 @@ class OpenVikingMemoryProvider(MemoryProvider):
logger.debug("Could not safely mark OpenViking session %s pending without a run lock", sid)
return
try:
path.parent.mkdir(parents=True, exist_ok=True)
from hermes_constants import mkdir_under_hermes_home
mkdir_under_hermes_home(path.parent)
atomic_json_write(path, {"session_id": sid, "owner_run_id": self._run_id}, mode=0o600)
self._pending_marked_sids.add(sid)
except Exception as e:
+2 -1
View File
@@ -321,7 +321,8 @@ def _mirror_manual_config_to_openviking_store(*, prompt, select, cancelled, valu
return _SETUP_CANCELLED
if replace is False:
continue
path.parent.mkdir(parents=True, exist_ok=True)
from hermes_constants import mkdir_under_hermes_home
mkdir_under_hermes_home(path.parent)
# atomic_json_write creates the temp file 0600 and os.replace()s it: no
# half-written config on crash, no chmod-after-write window for the keys.
ov.atomic_json_write(path, ov._ovcli_data_from_connection_values(values), mode=0o600)
+2 -1
View File
@@ -3449,7 +3449,8 @@ class FeishuAdapter(BasePlatformAdapter):
def _persist_seen_message_ids(self) -> None:
try:
self._dedup_state_path.parent.mkdir(parents=True, exist_ok=True)
from hermes_constants import mkdir_under_hermes_home
mkdir_under_hermes_home(self._dedup_state_path.parent)
with self._dedup_lock:
recent = self._seen_message_order[-self._dedup_cache_size:]
# Save as {msg_id: timestamp} so TTL filtering works across restarts.
+2 -1
View File
@@ -195,7 +195,8 @@ def _chmod_quiet(path: Path, mode: int) -> None:
def _write_private_json(path: Path, data: Any) -> None:
"""Atomically write JSON with 0o600 permissions (0o700 parent) where supported."""
path.parent.mkdir(parents=True, exist_ok=True)
from hermes_constants import mkdir_under_hermes_home
mkdir_under_hermes_home(path.parent)
_chmod_quiet(path.parent, 0o700)
# mkstemp's 0o600 temp + atomic rename never exposes the token at process umask.
atomic_write_text(path, json.dumps(data, indent=2, ensure_ascii=False), create_mode=0o600)