hermes - ✅(Solved) Fix bug: TOCTOU race in get_honcho_client() allows duplicate Honcho client construction [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#24759Fetched 2026-05-14 03:51:58
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
labeled ×3cross-referenced ×1

plugins/memory/honcho/client.py exposes get_honcho_client() — a lazy-init accessor for the process-wide _honcho_client singleton (line 665–776). The check-before-init pattern is:

_honcho_client: Honcho | None = None

def get_honcho_client(config=None) -> Honcho:
    global _honcho_client
    if _honcho_client is not None:
        return _honcho_client
    # ... full config resolution + lazy SDK install ...
    _honcho_client = Honcho(**kwargs)
    return _honcho_client

There is no lock around the check-then-initialize sequence. When two threads call get_honcho_client() concurrently before the singleton is set, both pass the is not None guard, both run the expensive initialization (config loading, optional lazy SDK install, Honcho(**kwargs) construction), and the second write overwrites the first — leaving an open connection leaked.

Root Cause

plugins/memory/honcho/client.py exposes get_honcho_client() — a lazy-init accessor for the process-wide _honcho_client singleton (line 665–776). The check-before-init pattern is:

_honcho_client: Honcho | None = None

def get_honcho_client(config=None) -> Honcho:
    global _honcho_client
    if _honcho_client is not None:
        return _honcho_client
    # ... full config resolution + lazy SDK install ...
    _honcho_client = Honcho(**kwargs)
    return _honcho_client

There is no lock around the check-then-initialize sequence. When two threads call get_honcho_client() concurrently before the singleton is set, both pass the is not None guard, both run the expensive initialization (config loading, optional lazy SDK install, Honcho(**kwargs) construction), and the second write overwrites the first — leaving an open connection leaked.

Fix Action

Fix

Add import threading, _honcho_client_lock = threading.Lock(), and apply double-checked locking in get_honcho_client().

PR fix notes

PR #24763: fix(honcho): prevent TOCTOU race in get_honcho_client()

Description (problem / solution / changelog)

Summary

  • get_honcho_client() in plugins/memory/honcho/client.py used an unguarded check-then-initialize pattern: two concurrent callers could both pass the is not None guard, both run config resolution + lazy SDK install + Honcho(**kwargs) construction, and the second would overwrite the first — leaking an open connection.
  • Added import threading and _honcho_client_lock = threading.Lock(). Applied double-checked locking: fast lock-free early return when already set, then full initialization (including the expensive config/import path) runs under the lock with a re-check at the top.
  • Added TestGetHonchoClientToctouRace (2 tests): 50-thread barrier verifying all concurrent callers receive the same instance, and a spy Honcho class confirming the constructor fires exactly once.

Closes #24759

Test plan

  • uv run python -m pytest tests/test_honcho_client_config.py::TestGetHonchoClientToctouRace -v — 2 passed
  • Existing test_honcho_client_config.py suite unaffected

🤖 Generated with Claude Code

Changed files

  • plugins/memory/honcho/client.py (modified, +94/-88)
  • tests/test_honcho_client_config.py (modified, +73/-0)

Code Example

_honcho_client: Honcho | None = None

def get_honcho_client(config=None) -> Honcho:
    global _honcho_client
    if _honcho_client is not None:
        return _honcho_client
    # ... full config resolution + lazy SDK install ...
    _honcho_client = Honcho(**kwargs)
    return _honcho_client
RAW_BUFFERClick to expand / collapse

Description

plugins/memory/honcho/client.py exposes get_honcho_client() — a lazy-init accessor for the process-wide _honcho_client singleton (line 665–776). The check-before-init pattern is:

_honcho_client: Honcho | None = None

def get_honcho_client(config=None) -> Honcho:
    global _honcho_client
    if _honcho_client is not None:
        return _honcho_client
    # ... full config resolution + lazy SDK install ...
    _honcho_client = Honcho(**kwargs)
    return _honcho_client

There is no lock around the check-then-initialize sequence. When two threads call get_honcho_client() concurrently before the singleton is set, both pass the is not None guard, both run the expensive initialization (config loading, optional lazy SDK install, Honcho(**kwargs) construction), and the second write overwrites the first — leaving an open connection leaked.

Impact

In multi-threaded agent sessions sharing one process (e.g. delegated tool calls, background workers), Honcho clients can be constructed multiple times, leaking connections and causing non-deterministic behavior if the first client established state (e.g. session contexts) that the replacement doesn't know about.

Fix

Add import threading, _honcho_client_lock = threading.Lock(), and apply double-checked locking in get_honcho_client().

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING