NousResearch/hermes-agent · error · RuntimeError
iron-proxy log {log_path} has unexpected owner uid={st.st_ui
Error message
iron-proxy log {log_path} has unexpected owner uid={st.st_uid}; refusing to write. What it means
After opening the iron-proxy log file (and fchmod'ing it to 0600), start_proxy fstat()s the fd and verifies st_uid matches the current effective uid, mirroring the pidfile ownership check. If the file on disk is owned by another uid (e.g. a pre-existing file created by root or another user, or a planted file at the log path), it raises rather than letting the proxy write into a file the current user does not own. This is a defensive check against symlink/file-planting attacks on HERMES_HOME paths.
Source
Thrown at agent/proxy_sources/iron_proxy.py:1856
log_open_flags |= os.O_NOFOLLOW
try:
log_fd = os.open(str(log_path), log_open_flags, 0o600)
except OSError as exc:
# ELOOP from a planted symlink — refuse with a clear error.
raise RuntimeError(
f"Refusing to write iron-proxy log {log_path}: {exc}. "
"Remove that path manually and retry."
) from exc
try:
os.fchmod(log_fd, 0o600) # tighten if file pre-existed
except OSError:
pass
# Verify ownership — same st_uid check the pidfile uses.
try:
st = os.fstat(log_fd)
if hasattr(os, "getuid") and st.st_uid != os.getuid():
os.close(log_fd)
raise RuntimeError(
f"iron-proxy log {log_path} has unexpected owner "
f"uid={st.st_uid}; refusing to write."
)
except AttributeError:
pass # Windows
try:
# Use the fd directly via the dup mechanism; Popen will dup() it
# into the child so we can close ours unconditionally below.
# NOTE: on Windows ``start_new_session`` is invalid; we don't
# support Windows for the proxy (the binary itself doesn't ship)
# but the kwarg is POSIX-only and silently ignored on Win.
popen_kwargs: Dict = dict(
env=env,
stdin=subprocess.DEVNULL,
stdout=log_fd,
stderr=subprocess.STDOUT,
)View on GitHub (pinned to c896c09c42)
Solutions
- Remove or reown the offending log file: find the path from the error message and `rm <log_path>` (or `chown $(id -u) <log_path>`), then retry `hermes egress start`
- If the whole state dir is mis-owned, fix it once: `chown -R $(id -u):$(id -g) <proxy-state-dir>`
- Never run the egress proxy under sudo/root; if you did, clean up all files it created in the state dir
- Verify no symlink exists at the log path (`ls -l <log_path>`) — a planted symlink is treated the same way
Example fix
# before: log file owned by root after a sudo run # RuntimeError: iron-proxy log ... unexpected owner uid=0 sudo rm /home/user/.hermes/iron-proxy/proxy.log hermes egress start # after: start succeeds, log recreated with current uid
Defensive patterns
Strategy: validation
Validate before calling
import os, stat
from pathlib import Path
def log_path_safe(p: Path) -> bool:
if p.is_symlink():
return False
try:
st = p.stat()
except FileNotFoundError:
return os.stat(p.parent).st_uid == os.getuid() # parent owned by us
return st.st_uid == os.getuid() and not stat.S_ISLNK(st.st_mode)
# before start_proxy(): assert log_path_safe(log_path) else remove/fix it Type guard
def is_owned_log(p: Path) -> bool:
try:
st = p.lstat()
except FileNotFoundError:
return True
return st.st_uid == os.getuid() and not (st.st_mode & 0o170000) == 0o120000 Try / catch
try:
start_proxy(...)
except RuntimeError as e:
if "unexpected owner" in str(e):
log_path = Path(str(e).split()[3]) # remove/reown, then retry once
os.remove(log_path)
start_proxy(...) Prevention
- Never run `hermes egress start` under sudo — mixed-uid file creation is the top cause
- After restoring or copying a HERMES_HOME, run `chown -R $(id -u)` over it
- Pre-flight: lstat the log path before start and require uid match / no symlink
When it happens
Trigger: Calling start_proxy() when the log path (under the iron-proxy state dir) already exists and is owned by a different uid — e.g. a previous root-run of `hermes egress start`, restoring a state dir with sudo/cp -a, or a hostile pre-created file. Only fires on POSIX (guarded by hasattr(os, 'getuid')).
Common situations: Running `sudo hermes egress start` once and then running as the normal user; copying ~/.hermes between users with ownership preserved; shared machines where another account wrote to the same HERMES_HOME; CI containers that pre-create dirs as a different uid.
Related errors
- Unsafe SSH control dir: ${controlDir} is owned by uid ${st.u
- Refusing to start: could not pre-create audit log {audit_pat
- Refusing to write iron-proxy log {log_path}: {exc}. Remove
- Refusing to write pidfile {pidfile}: {exc}. Remove that pat
- pidfile {pidfile} has unexpected owner uid={st.st_uid}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/e183ee6b86bf21eb.
Report an issue: GitHub.