hermes - 💡(How to fix) Fix concurrency: providers/_discover_providers() sets _discovered=True before registry is populated (TOCTOU) [1 pull requests]

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…

Fix Action

Fixed

Code Example

def _discover_providers() -> None:
    global _discovered
    if _discovered:
        return
    _discovered = True          # ← flag set HERE, registry still empty
    # 1. Bundled plugins — directory scan + imports
    # 2. User plugins — directory scan + imports
    # 3. Legacy per-file modules

---

def get_provider_profile(name: str) -> ProviderProfile | None:
    if not _discovered:
        _discover_providers()
    canonical = _ALIASES.get(name, name)
    return _REGISTRY.get(canonical)   # ← returns None for valid provider
RAW_BUFFERClick to expand / collapse

Bug

providers/__init__.py::_discover_providers() sets _discovered = True at line 154 before any plugin imports run — before _REGISTRY and _ALIASES are populated.

def _discover_providers() -> None:
    global _discovered
    if _discovered:
        return
    _discovered = True          # ← flag set HERE, registry still empty
    # 1. Bundled plugins — directory scan + imports
    # 2. User plugins — directory scan + imports
    # 3. Legacy per-file modules

Race window: Thread A sets _discovered = True, Thread B enters get_provider_profile("nvidia"), sees the flag, skips discovery, reads an empty _REGISTRY, and returns None. Unlike agent/transports.get_transport(), get_provider_profile() has no retry on miss — it returns None directly.

def get_provider_profile(name: str) -> ProviderProfile | None:
    if not _discovered:
        _discover_providers()
    canonical = _ALIASES.get(name, name)
    return _REGISTRY.get(canonical)   # ← returns None for valid provider

A None profile causes the call site to fall back to generic provider configuration (no model-specific routing, temperature rules, or context limits).

Fix: Add _discover_lock = threading.Lock() and apply double-checked locking — fast lock-free path when already discovered, slow path acquires lock, re-checks, runs all imports, then sets _discovered = True last.

Affected files

  • providers/__init__.py_discover_providers(), get_provider_profile(), list_providers()

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

hermes - 💡(How to fix) Fix concurrency: providers/_discover_providers() sets _discovered=True before registry is populated (TOCTOU) [1 pull requests]