NousResearch/hermes-agent

Stale connection and PID file handling patches

Cluster 14598 · gateway. Data as of

Nothing open on GitHub. Fix merged in #41974 on 8 Jun 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #41974. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this unresolved. That is separate from the fix event.
Still open
Nothing open on GitHub.

These items address stale or leaked connections and PID file handling across the gateway and Bedrock components, including cache eviction cleanup, RSS memory leaks, and recovery from SIGKILL/OOM scenarios. No single fix is assumed complete or final.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #41974 fix(gateway): release evicted agent clients to stop RSS leak (#29298)

    merged to main

    files: gateway/run.py

    Show the diff
    • gateway/run.py
      @@ -12493,25 +12493,67 @@ async def _interrupt_and_clear_session(
                   self._release_running_agent_state(session_key)
       
           def _evict_cached_agent(self, session_key: str) -> None:
      -        """Remove a cached agent for a session (called on /new, /model, etc)."""
      +        """Remove a cached agent for a session (called on /new, /model, etc).
      +
      +        Pops the entry AND soft-releases the evicted agent's LLM client
      +        pool so the httpx connection (sockets + held buffers) is freed
      +        promptly rather than waiting on CPython GC — AIAgent holds
      +        reference cycles (callbacks, tool state) that delay refcount
      +        collection, so a manual release is required to keep gateway RSS
      +        flat across many /new, /model, undo and reset operations (#29298,
      +        same leak class as #25315).
      +
      +        The release is soft (``release_clients()``): it frees the client
      +        pool and per-turn child subagents but PRESERVES the session's
      +        terminal sandbox, browser daemon, and tracked bg processes (keyed
      +        on task_id), because the session may resume with a freshly-built
      +        agent.  Call sites that want a hard teardown (true conversation
      +        boundaries like /new) already call ``_cleanup_agent_resources``
      +        before evicting; ``release_clients`` is idempotent and safe to
      +        run again after that (the client is already None).
      +
      +        Cleanup runs on a daemon thread so we never block holding
      +        ``_agent_cache_lock`` on slow socket teardown — mirrors the
      +        cap-enforcer and idle-sweeper paths.
      +        """
               _lock = getattr(self, "_agent_cache_lock", None)
      +        evicted = None
               if _lock:
                   with _lock:
      -                entry = self._agent_cache.pop(session_key, None)
      -            # Release clients on a daemon thread, same as _enforce_agent_cache_cap.
      -            # Without this, every /new, /model, /reasoning, codex-runtime change,
      -            # and /undo leaked a full agent: OpenAI client, httpx transport, SSL
      -            # context, and conversation history. Only the /new path cleaned up
      -            # first; the rest popped the entry and dropped it on the floor.
      -            if entry is not None:
      -                agent = entry[0] if isinstance(entry, tuple) and entry else None
      -                if agent is not None:
      -                    threading.Thread(
      -                        target=self._release_evicted_agent_soft,
      -                        args=(agent,),
      -                        daemon=True,
      -                        name=f"agent-cache-cmd-evict-{session_key[:24]}",
      -                    ).start()
      +                evicted = self._agent_cache.pop(session_key, None)
      +        else:
      +            _cache = getattr(self, "_agent_cache", None)
      +            if _cache is not None:
      +                evicted = _cache.pop(session_key, None)
      +
      +        agent = evicted[0] if isinstance(evicted, tuple) and evicted else evicted
      +        if agent is None or agent is _AGENT_PENDING_SENTINEL:
      +            return
      +
      +        # Don't tear down an agent that's actively mid-turn — its client,
      +        # sandbox and child subagents are in use by the running request.
      +        running_ids = {
      +            id(a)
      +            for a in getattr(self, "_running_agents", {}).values()
      +            if a is not None and a is not _AGENT_PENDING_SENTINEL
      +        }
      +        if id(agent) in running_ids:
      +            return
      +
      +        try:
      +            threading.Thread(
      +                target=self._release_evicted_agent_soft,
      +                args=(agent,),
      +                daemon=True,
      +                name=f"agent-evict-{str(session_key)[:24]}",
      +            ).start()
      +        except Exception:
      +            # If we can't spawn a thread (interpreter shutdown), release
      +            # inline as a best-effort fallback.
      +            try:
      +                self._release_evicted_agent_soft(agent)
      +            except Exception:
      +                pass
       
           @staticmethod
           def _init_cached_agent_for_turn(agent: Any, interrupt_depth: int) -> None:

2 PRs closed without merging

From the close comments: done elsewhere 2

  • #14609 fix(gateway): recover stale pid files and release evicted clients · done elsewhere → 402d048, #14598, #29298, #41974
  • #14710 fix(bedrock): evict cached boto3 client on stale-connection errors · done elsewhere → a9ccb03
Closed issues · 1
  • Closed#14598Gateway run.py: PID file race condition and httpx connection leak on cache eviction
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
gateway
Merged PRs
#41974
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
8 Jun 2026

For your agent

The same facts, typed.