{"record":{"id":"aa752b864e6c9b4b","repo":"NousResearch/hermes-agent","slug":"another-iron-proxy-start-appears-to-be-in-progress","errorCode":null,"errorMessage":"Another iron-proxy start appears to be in progress (pidfile {pidfile} -> pid {existing_pid}).  Run `hermes egress stop` if that proxy is stuck.","messagePattern":"Another iron-proxy start appears to be in progress \\(pidfile (.+?) -> pid (.+?)\\)\\.  Run `hermes egress stop` if that proxy is stuck\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/proxy_sources/iron_proxy.py","lineNumber":2029,"sourceCode":"    Side effect: also persists the in-process nonce to disk so\n    cross-CLI-invocation ``_pid_alive`` checks (start in one process,\n    stop in another) can still defeat PID recycling.\n    \"\"\"\n    open_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL\n    if hasattr(os, \"O_NOFOLLOW\"):\n        open_flags |= os.O_NOFOLLOW\n    try:\n        fd = os.open(str(pidfile), open_flags, 0o600)\n    except FileExistsError:\n        # Pidfile already exists.  If it points at a live iron-proxy,\n        # caller's _read_pid + _pid_alive at the top of start_proxy\n        # should already have returned.  Reaching here means EITHER\n        # the previous _pid_alive check raced (rare; another start in\n        # flight), OR a stale pidfile survived a crash.  Discriminate\n        # and retry once with O_TRUNC if stale.\n        existing_pid = _read_pid()\n        if existing_pid and _pid_alive(existing_pid):\n            raise RuntimeError(\n                f\"Another iron-proxy start appears to be in progress \"\n                f\"(pidfile {pidfile} -> pid {existing_pid}).  \"\n                f\"Run `hermes egress stop` if that proxy is stuck.\"\n            )\n        # Stale — unlink and retry.\n        try:\n            pidfile.unlink()\n        except FileNotFoundError:\n            pass\n        fd = os.open(str(pidfile), open_flags, 0o600)\n    except OSError as exc:\n        # ELOOP from a planted symlink at the pidfile path.\n        raise RuntimeError(\n            f\"Refusing to write pidfile {pidfile}: {exc}.  \"\n            \"Remove that path manually and retry.\"\n        ) from exc\n\n    try:","sourceCodeStart":2011,"sourceCodeEnd":2047,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/proxy_sources/iron_proxy.py#L2011-L2047","documentation":"_write_pidfile_safely opened with O_EXCL and got FileExistsError, and the existing pidfile resolves to a pid that _pid_alive() considers live. Since the top-of-start liveliness check should have caught this, reaching here means a genuine race with another concurrent start, or a recycled/foreign pid. The raise tells the operator to stop the existing instance.","triggerScenarios":"Two concurrent `hermes egress start` invocations (shell + cron, double Enter in a script); a start racing the top-of-function _read_pid/_pid_alive check; a stale pidfile whose number now belongs to any live unrelated process (pid reuse).","commonSituations":"A systemd unit and a manual start firing together; an automation script that starts without checking status; long uptime allowing pid-space wraparound so the stale pidfile's number matches an innocent process.","solutions":["Run `hermes egress stop` to clear the live/stuck instance and its pidfile, then start again","If `stop` refuses because the pid is not really an iron-proxy (pid reuse), remove the pidfile manually and confirm no proxy is running (`ps -p <pid>`)","Serialize starts in automation: check `hermes egress status` before `start`, or take an external lock"],"exampleFix":"# before: concurrent starts\nhermes egress start & hermes egress start &   # second raises 385\nhermes egress stop && hermes egress start\n\n# after: single clean start","handlingStrategy":"validation","validationCode":"# emulate the top-of-start check before invoking start_proxy:\nfrom agent.proxy_sources import iron_proxy\n\ndef no_live_proxy(pidfile) -> bool:\n    pid = iron_proxy._read_pid()\n    return not (pid and iron_proxy._pid_alive(pid))\n\n# if not no_live_proxy(...): run `hermes egress stop` instead of start","typeGuard":null,"tryCatchPattern":"try:\n    start_proxy(...)\nexcept RuntimeError as e:\n    if 'Another iron-proxy start' in str(e):\n        stop_proxy()          # hermes egress stop\n        start_proxy(...)      # one retry after clean stop","preventionTips":["In automation, always `status` or `stop` before `start`; never fire concurrent starts","If a stale pidfile names a live unrelated pid (reuse), verify with `ps -p <pid> -o cmd=` before removing it","Wrap start in an flock so parallel invocations serialize"],"tags":["egress-proxy","concurrency","pidfile"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}