Skip to content

fix(skills): preserve _skill_commands cache when scan fails (#18659) - #74511

Open
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/18659-skill-command-cache
Open

fix(skills): preserve _skill_commands cache when scan fails (#18659)#74511
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/18659-skill-command-cache

Conversation

@Tranquil-Flow

Copy link
Copy Markdown
Contributor

Summary

Preserve the last-known-good skill slash-command cache when scan_skill_commands() encounters an outer scan failure. The new command map and platform marker are built locally and published together only after a successful scan; failed scans retain the prior cache and emit a diagnostic warning. This also prevents overlapping scans from observing another scan's partial map.

Fixes #18659.

Verification

  • /Users/evinova-self/.hermes/hermes-agent/venv/bin/python3 -m pytest tests/agent/test_skill_commands.py -v -o "addopts=" --tb=short27 passed
  • /Users/evinova-self/.hermes/hermes-agent/venv/bin/python3 -m pytest tests/gateway/test_stacked_skill_platform_disabled.py tests/agent/test_skill_invocation_description.py tests/agent/test_skill_commands_reload.py tests/test_session_skill_previews.py -v -o "addopts=" --tb=short16 passed
  • ruff check agent/skill_commands.py tests/agent/test_skill_commands.py — passed
  • git diff --check upstream/main...HEAD — passed

The branch is one focused commit ahead of current upstream/main (36f885573c5d054fbb3827e5d45f235cf42fd791).

Competitor analysis

Open competitors #18668 and #18720 preserve only the command map and omit atomic staging of _skill_commands_platform; both therefore can label a stale platform-filtered cache as current after a failed platform transition and suppress the required retry. #18668 also has a failed test check and is dirty. #18735 is a contaminated seven-issue bundle rather than a focused competitor. This PR covers the platform-marker, reload, concurrent-scan, successful-empty-scan, and diagnostic-warning layers with production-path regression tests.

Auto-published by Moonsong via Path B automated pipeline.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/skills Skills system (list, view, manage) P2 Medium — degraded but workaround exists needs-decision Awaiting maintainer decision before any implementation labels Jul 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #18668 and #18720 for #18659. Their current heads preserve only the map; this patch also stages the platform marker and covers the failed platform-transition retry, so maintainers should choose the implementation.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the real cache-loss path: current main clears the cache before entering the outer scan guard at agent/skill_commands.py:381-383, so an outer failure leaves no skill commands.

Problems

  • The proposed publication is not atomic: agent/skill_commands.py:472-473 writes the map and platform marker separately, while get_skill_commands() reads them separately at agent/skill_commands.py:484-489. Concurrent scans can still retain a map from one platform with another platform's marker, suppressing the required retry.
  • tests/agent/test_skill_commands.py:335-367 synchronizes construction but does not force or assert that publication/read interleaving, so it cannot catch this remaining race.

Suggested changes

  • Publish/read map and platform as one synchronized snapshot (for example, under a shared lock or immutable snapshot object).
  • Add a deterministic concurrent regression asserting the map and platform marker always remain paired.

This is an automated hermes-sweeper review.

Comment thread agent/skill_commands.py
exc_info=True,
)
return _skill_commands
_skill_commands = new_commands

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an atomic publish with line 473: another scan or get_skill_commands() can run between the two global assignments and observe a new map with the previous platform marker (or vice versa). Publish and read both values through one synchronized snapshot, then add a test that forces this interleaving.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@Tranquil-Flow
Tranquil-Flow force-pushed the fix/18659-skill-command-cache branch from 9c5c841 to e94325e Compare August 4, 2026 22:12
…arch#18659)

The unconditional ``_skill_commands = {}`` and ``_skill_commands_platform``
assignment at the top of ``scan_skill_commands()`` (and the
``except Exception: pass`` that swallowed the failure) caused a transient
scan failure to wipe every skill slash command and trigger a misleading
``/reload-skills`` diff reporting 90+ skills as removed. The
``_skill_commands_platform`` marker (added later for NousResearch#14536) was
also written before the scan, so a failed scan for a new platform
would have left the old platform-filtered mapping labelled as the
requested platform and suppressed the required retry.

Build the new map and platform marker in local variables and only
commit them to module state when the entire scan completes without
exception. On outer failure, return the previous cache unchanged and
log a diagnostic warning so the user can see why their skills did
not refresh.

Add regression tests that exercise:
- Outer scan failure preserves the command map and platform marker.
- Successful empty scan still replaces the cache.
- ``/reload-skills`` on a failed scan does not report survivors as
  removed.
- The diagnostic warning is emitted (and not spammed across retries).
- Overlapping scans do not share a partial command map.

Fixes NousResearch#18659.

Build context:
- base: upstream/main @ 4d9541b
- layers: 7/7 (failure preservation, mid-scan failure, atomic
  replacement, platform coherence, reload semantics, concurrent
  isolation, failure observability)
- RED proof: 4 of 5 new tests fail on upstream/main
- GREEN: 27/27 tests in tests/agent/test_skill_commands.py pass
- regression scope: 107 tests across skill_commands + kanban DB
  + state DB + session preview modules pass
- ruff: clean
- excluded pre-existing flaky: test_tui_gateway_server.py
  ::test_write_json_serializes_concurrent_writes (reproduces 10/10
  on upstream/main without the fix; documented as unrelated)
@Tranquil-Flow
Tranquil-Flow force-pushed the fix/18659-skill-command-cache branch from e94325e to b192929 Compare August 7, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/skills Skills system (list, view, manage) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: scan_skill_commands unconditionally clears _skill_commands before try block, silently loses all skills on scan failure

3 participants