Skip to content

fix(tools): prefer load_config() over stale CLI_CONFIG in delegate _load_config()#21405

Open
shellybotmoyer wants to merge 1 commit into
NousResearch:mainfrom
shellybotmoyer:fix/delegate-config-stale-cache-rebased
Open

fix(tools): prefer load_config() over stale CLI_CONFIG in delegate _load_config()#21405
shellybotmoyer wants to merge 1 commit into
NousResearch:mainfrom
shellybotmoyer:fix/delegate-config-stale-cache-rebased

Conversation

@shellybotmoyer

Copy link
Copy Markdown
Contributor

Problem

hermes config set delegation.* writes to disk and reports success, but _load_config() in delegate_tool.py checks the in-memory CLI_CONFIG dict first. Since CLI_CONFIG was loaded once at startup and never invalidated, the stale cached value wins — the new disk config is never read until process restart.

This affects CLI, gateway, and cron — any long-running process calling delegate_task() after a config change via hermes config set.

Root Cause

_load_config() prioritized CLI_CONFIG (stale in-memory dict) over load_config() (mtime-cached disk reads with automatic invalidation). The load_config() function in hermes_cli/config.py already handles cache invalidation correctly by checking (mtime_ns, size) — it just was never reached because CLI_CONFIG was checked first.

Fix

Reverse priority so load_config() (mtime-aware, always fresh) is tried before CLI_CONFIG (stale startup snapshot). Added if cfg: guard to the fallback path for consistency.

Testing

  1. Start a gateway session with delegation.model: google/gemini-2.5-flash
  2. From another shell: hermes config set delegation.model google/gemini-3-flash-preview
  3. Call delegate_task(...) from the original session
  4. Before fix: subagent uses gemini-2.5-flash (stale cache)
  5. After fix: subagent uses gemini-3-flash-preview (fresh from disk)

Notes

This replaces PR #18978 which was 692+ commits behind main and showed DIRTY merge state. Clean rebase onto current main.

Fixes #18946

…oad_config()

Fixes NousResearch#18946: hermes config set delegation.* silently has no effect on running process because _load_config() checks CLI_CONFIG first (stale in-memory dict) before load_config() (mtime-cached disk reads). Reverse priority so disk config with mtime invalidation wins.
@alt-glitch alt-glitch added type/bug Something isn't working comp/tools Tool registry, model_tools, toolsets tool/delegate Subagent delegation area/config Config system, migrations, profiles P2 Medium — degraded but workaround exists labels May 7, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #12053 — same root cause (delegate_tool._load_config() reads stale CLI_CONFIG instead of disk), same fix (invert priority to load_config() first). Prior attempts: #8004, #15540, #18947, #18967, #18978. Consider closing in favor of #12053 which has been open longest.

@foundation-nnet

Copy link
Copy Markdown

Edge case: priority reversal can still trap on empty-string values

Thanks for the fix — inverting the priority to prefer load_config() (mtime-cached disk) over CLI_CONFIG is a solid improvement over the original bug.

However, the same truthy empty-string trap can still occur with this approach. If a user has:

delegation:
  model: ""
  provider: ""

full.get("delegation") returns {"model": "", "provider": ""} — a non-empty dict that passes if cfg:. The function returns it immediately, never falling back to CLI_CONFIG for sensible defaults. Parent inheritance then fails because the keys exist but their values are empty.

A merge-based approach avoids this entirely:

def _load_config() -> dict:
    result = {}
    try:
        from cli import CLI_CONFIG
        result = dict(CLI_CONFIG.get("delegation") or {})
    except Exception:
        pass
    try:
        from hermes_cli.config import load_config
        full = load_config()
        persisted = full.get("delegation") or {}
        for key, value in persisted.items():
            if value or isinstance(value, (bool, int)):
                result[key] = value
    except Exception:
        pass
    return result

CLI_CONFIG provides defaults, persistent config overrides when non-empty. No truthy/empty-string ambiguity — model: "" is simply ignored and the default fills in. max_iterations: 50 (int) and inherit_mcp_toolsets: true (bool) are preserved.

This is the approach I proposed in #32671. Would you be open to adopting the merge strategy, or shall I submit a separate PR with it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: hermes config set delegation.* silently has no effect on running process (CLI_CONFIG cache stale)

3 participants