NousResearch/hermes-agent · error · RuntimeError
Refusing to start: could not pre-create audit log {audit_pat
Error message
Refusing to start: could not pre-create audit log {audit_path} with restrictive permissions ({exc}). Move or chmod any existing file at that path and retry. What it means
Before launching the daemon, start_proxy() pre-creates the audit log with O_WRONLY|O_CREAT|O_APPEND (+O_NOFOLLOW where available) and force-chmods it 0600, so the audit trail is never world-readable and never follows a planted symlink. If the os.open/fchmod fails (wrong permissions on an existing file, symlink attack ELOOP, unwritable directory, SELinux denial), startup is refused rather than proceeding with a weakened audit trail.
Source
Thrown at agent/proxy_sources/iron_proxy.py:1352
file is non-load-bearing until the version bump — but the qualified
message keeps operators from wiring monitoring to a path that can't
exist.
"""
try:
# Use os.open + O_CREAT to avoid races on the chmod.
open_flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
if hasattr(os, "O_NOFOLLOW"):
open_flags |= os.O_NOFOLLOW
fd = os.open(str(audit_path), open_flags, 0o600)
try:
# Tighten perms even if the file already existed under a
# slacker umask.
os.fchmod(fd, 0o600)
finally:
os.close(fd)
except OSError as exc:
raise RuntimeError(
f"Refusing to start: could not pre-create audit log "
f"{audit_path} with restrictive permissions ({exc}). "
f"Move or chmod any existing file at that path and retry."
) from exc
def write_proxy_config(config: Dict) -> Path:
"""Serialize the config dict to ``<hermes_home>/proxy/proxy.yaml``.
Uses ``yaml.safe_dump`` so we never emit Python tags.
"""
try:
import yaml # PyYAML is already a Hermes dep
except ImportError as exc:
raise RuntimeError(
"PyYAML is required to write the iron-proxy config but is not "
"installed."View on GitHub (pinned to c896c09c42)
Solutions
- Inspect the path in the message: `ls -l <audit_path>` — if it's a symlink or foreign-owned file, remove or chown/chmod it (chmod 600) and retry.
- Ensure the parent proxy state directory is owned by the user running Hermes.
- Don't run `hermes egress start` under mixed sudo/non-sudo invocations; pick one identity.
Defensive patterns
Strategy: validation
Validate before calling
import os
from pathlib import Path
def audit_log_writable(path: Path) -> bool:
if path.is_symlink():
return False
if path.exists():
st = path.stat()
return os.geteuid() == st.st_uid and (st.st_mode & 0o600 == 0o600)
return os.access(path.parent, os.W_OK) Try / catch
try:
start_proxy()
except RuntimeError as e:
if "audit log" in str(e):
# fix perms/ownership of the named path, then retry
raise Prevention
- Run all egress commands as one identity — never mix sudo and non-sudo against the same state dir.
- Ensure the proxy state dir is user-owned before first start.
- Never symlink the audit log path.
When it happens
Trigger: start_proxy() / `hermes egress start` when the audit log path exists with wrong ownership/perms (e.g. created earlier by root, or 0644 with a sticky context), is a symlink (O_NOFOLLOW → ELOOP), or its parent dir is not writable by the current user.
Common situations: Running the daemon once under sudo then again as the normal user; restored backups that lost ownership; security modules (SELinux) denying the open; a same-uid attacker planting a symlink.
Related errors
- Refusing to write iron-proxy log {log_path}: {exc}. Remove
- Unsafe SSH control dir: ${controlDir} is owned by uid ${st.u
- iron-proxy log {log_path} has unexpected owner uid={st.st_ui
- Hermes backend did not become ready: ${detail}
- Could not create directory: ${error.message}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/5a1f5c1ea15bb8b0.
Report an issue: GitHub.