hermes - ✅(Solved) Fix skill_commands cache can leak one platform's disabled-skill view into another [2 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#14536Fetched 2026-04-24 06:16:40
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×4cross-referenced ×2

Root Cause

Related context

This looks adjacent to profile/platform command-registration issues such as #8108, but the root cause here is the missing platform dimension in the in-process skill_commands cache itself.

Fix Action

Fixed

PR fix notes

PR #14570: fix(skill-commands): rescan cache when platform changes

Description (problem / solution / changelog)

Summary

  • rescan cached skill commands when the effective Hermes platform changes
  • keep disabled-skill filtering aligned with the current platform-scoped config
  • add a regression test that switches HERMES_PLATFORM within one process

Testing

  • python3 -m pytest -o addopts='' tests/agent/test_skill_commands.py -q

Closes #14536

Changed files

  • agent/skill_commands.py (modified, +22/-2)
  • tests/agent/test_skill_commands.py (modified, +35/-0)

PR #14594: fix(skills): key skill-command cache per platform to prevent cross-platform leaks

Description (problem / solution / changelog)

Problem

Closes #14536

agent/skill_commands.py maintains a process-global _skill_commands dict. The first platform to call scan_skill_commands() seeds this cache with its own platform-specific disabled-skill view. Subsequent platforms (e.g. Discord after Telegram) hit get_skill_commands(), find the cache non-empty, and return the wrong skill set — inheriting the first platform's disabled list.

Root Cause

# Before
_skill_commands: Dict[str, Dict[str, Any]] = {}  # single global cache

def get_skill_commands():
    if not _skill_commands:   # any non-empty cache → returned as-is for ALL platforms
        scan_skill_commands()
    return _skill_commands

_get_disabled_skill_names() correctly resolves per-platform disabled lists via HERMES_SESSION_PLATFORM, but the cached result was shared across all platforms.

Fix

BeforeAfter
Cache structureDict[str, Any] (flat)Dict[str | None, Dict[str, Any]] (per-platform)
scan_skill_commands()no platform argaccepts platform=None, resolves and uses as cache key
get_skill_commands()no platform argaccepts platform=None, checks per-platform slot
Platform resolutionalways from envexplicit arg → HERMES_SESSION_PLATFORMHERMES_PLATFORMNone

All existing call-sites use the no-arg form and continue to work — they now automatically resolve platform from session context.

Tests Added (TestPlatformAwareCache)

  • test_different_platforms_get_independent_caches — Telegram and Discord see their own disabled lists
  • test_platform_none_uses_global_disabled_list — global disabled list respected when platform is None
  • test_rescan_updates_platform_cache_entry — rescan replaces the per-platform slot

Changed files

  • agent/skill_commands.py (modified, +47/-11)
  • tests/agent/test_skill_commands.py (modified, +95/-0)

Code Example

source venv/bin/activate
python - <<'PY'
import os, tempfile, textwrap
from pathlib import Path
from agent.skill_commands import scan_skill_commands, get_skill_commands

with tempfile.TemporaryDirectory() as td:
    home = Path(td)
    os.environ['HERMES_HOME'] = str(home)
    for name in ['alpha', 'beta']:
        d = home / 'skills' / name
        d.mkdir(parents=True)
        (d / 'SKILL.md').write_text(textwrap.dedent(f'''\
---
name: {name}
description: {name} skill
---
body
'''))

    (home / 'config.yaml').write_text(textwrap.dedent('''\
skills:
  platform_disabled:
    telegram: [alpha]
    discord: [beta]
'''))

    os.environ['HERMES_PLATFORM'] = 'telegram'
    print('telegram:', sorted(get_skill_commands().keys()))

    os.environ['HERMES_PLATFORM'] = 'discord'
    print('discord without rescan:', sorted(get_skill_commands().keys()))
    print('discord with explicit rescan:', sorted(scan_skill_commands().keys()))
PY

---

telegram: ['/beta']
discord without rescan: ['/beta']
discord with explicit rescan: ['/alpha']
RAW_BUFFERClick to expand / collapse

Bug Description

agent.skill_commands caches one global _skill_commands mapping, but the scan itself depends on platform-specific disabled-skill config via _get_disabled_skill_names(). In a long-lived multi-platform process, the first platform to scan commands wins; later platforms can inherit the wrong enabled/disabled skill set.

Affected files / lines

  • agent/skill_commands.py:206-212 — scan reads platform-aware disabled skill names
  • agent/skill_commands.py:265-269get_skill_commands() reuses the global cache until it is empty

Why this is a bug

The cache is process-global, but disabled-skill resolution is platform-specific. If telegram disables one set of skills and discord disables another, whichever platform scans first seeds the cache for the other.

This can cause wrong slash-command availability and stale skill menus across gateway platforms.

Minimal reproduction

source venv/bin/activate
python - <<'PY'
import os, tempfile, textwrap
from pathlib import Path
from agent.skill_commands import scan_skill_commands, get_skill_commands

with tempfile.TemporaryDirectory() as td:
    home = Path(td)
    os.environ['HERMES_HOME'] = str(home)
    for name in ['alpha', 'beta']:
        d = home / 'skills' / name
        d.mkdir(parents=True)
        (d / 'SKILL.md').write_text(textwrap.dedent(f'''\
---
name: {name}
description: {name} skill
---
body
'''))

    (home / 'config.yaml').write_text(textwrap.dedent('''\
skills:
  platform_disabled:
    telegram: [alpha]
    discord: [beta]
'''))

    os.environ['HERMES_PLATFORM'] = 'telegram'
    print('telegram:', sorted(get_skill_commands().keys()))

    os.environ['HERMES_PLATFORM'] = 'discord'
    print('discord without rescan:', sorted(get_skill_commands().keys()))
    print('discord with explicit rescan:', sorted(scan_skill_commands().keys()))
PY

Current output:

telegram: ['/beta']
discord without rescan: ['/beta']
discord with explicit rescan: ['/alpha']

Expected Behavior

Each platform should see its own disabled-skill view, even inside the same process.

Actual Behavior

get_skill_commands() can return a stale command set computed for a different platform.

Related context

This looks adjacent to profile/platform command-registration issues such as #8108, but the root cause here is the missing platform dimension in the in-process skill_commands cache itself.

Suggested investigation direction

  • Key _skill_commands by platform/home/config state, or
  • always rescan when platform context changes, or
  • move platform filtering out of the global cache and apply it per call.

extent analysis

TL;DR

The most likely fix involves modifying the agent/skill_commands.py to incorporate platform-specific caching or to rescan skill commands when the platform context changes.

Guidance

  • Modify the _skill_commands cache to be keyed by platform, allowing each platform to have its own cache of enabled/disabled skills.
  • Consider implementing a mechanism to rescan skill commands whenever the platform context changes, ensuring that the correct set of skills is always available.
  • Move the platform filtering logic out of the global cache and apply it per call to get_skill_commands(), ensuring that the correct skills are returned for each platform.
  • Investigate the use of a dictionary with platform as a key to store the skill commands, allowing for easy retrieval and updating of skills based on the current platform.

Example

_skill_commands = {}

def get_skill_commands(platform):
    if platform not in _skill_commands:
        _skill_commands[platform] = scan_skill_commands(platform)
    return _skill_commands[platform]

Notes

The provided minimal reproduction code demonstrates the issue and can be used as a starting point for testing potential fixes. The chosen solution should ensure that each platform sees its own disabled-skill view, even within the same process.

Recommendation

Apply a workaround by modifying the agent/skill_commands.py to use platform-specific caching, as this approach directly addresses the root cause of the issue and ensures that each platform has its own correct set of enabled/disabled skills.

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 - ✅(Solved) Fix skill_commands cache can leak one platform's disabled-skill view into another [2 pull requests, 1 participants]