Skip to content

fix(cli): scope USER_OWNED_EXCLUDE to root in profile distribution copy - #31123

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/profile-distribution-nested-basename-31033
Closed

briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/profile-distribution-nested-basename-31033

Conversation

@briandevans

Copy link
Copy Markdown

What does this PR do?

hermes_cli/profile_distribution.py::_copy_dist_payload filtered protected
basenames at every recursive depth via
shutil.copytree(..., ignore=lambda d, names: [n for n in names if n in USER_OWNED_EXCLUDE]).
The outer for entry in staged.iterdir() already drops root-level
user-owned entries (lines 545-549), so the inner lambda was redundant for
the protection use case but actively wrong for distribution-owned nested
paths that share a basename with a root-level protected entry — for
example skills/<category>/hermes-agent/SKILL.md. The nested skill
silently disappeared during hermes profile install --force /
hermes profile update.

Drop the per-depth ignore= argument. Root-scoped exclusion stays intact
via the existing outer-loop check at the top of _copy_dist_payload. The
sibling export path (hermes_cli/profiles.py::_default_export_ignore)
already implements the correct "root-only vs all-depth" split, so this
aligns the install/update path with the same intent.

Related Issue

Fixes #31033

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/profile_distribution.py — drop the ignore= lambda from the
    recursive shutil.copytree call inside _copy_dist_payload; comment
    documents the root-scoped intent.
  • tests/hermes_cli/test_profile_distribution.py — add
    TestUserOwnedExcludeRootScope with two regression cases:
    • test_nested_distribution_path_with_protected_basename_preserved —
      proves skills/<category>/hermes-agent/SKILL.md lands in the target
      profile.
    • test_root_protected_basename_still_excluded — proves a root-level
      hermes-agent/ in the staging payload is still skipped, so the fix
      does not weaken the root-scoped protection.

How to Test

  1. Build a staging payload that contains both
    skills/autonomous-ai-agents/hermes-agent/SKILL.md and a root-level
    hermes-agent/ directory.
  2. uv run --with pytest --with pytest-xdist --with pytest-asyncio --with pytest-timeout python3 -m pytest tests/hermes_cli/test_profile_distribution.py -v
  3. Expected: all 65 tests pass. Reverting the source change makes the two
    new tests fail with Nested distribution-owned hermes-agent/ was dropped.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A; behavior matches the docstring's stated intent
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — pure pathlib/shutil change, platform-neutral
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Related / Positioning

Audited siblings: hermes_cli/profiles.py::_default_export_ignore
already implements the correct "root-only" split with an explicit
if Path(directory) == root_dir: check. No widening needed in this
PR; the export path is already correct and the install/update path
now matches its intent.

Copilot AI review requested due to automatic review settings May 23, 2026 20:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes an install/update bug where USER_OWNED_EXCLUDE was incorrectly applied at all descendant depths (by basename), causing distribution-owned nested paths to be silently skipped during payload copy.

Changes:

  • Remove shutil.copytree(..., ignore=...) filtering so USER_OWNED_EXCLUDE is effectively root-scoped.
  • Add regression tests ensuring nested distribution paths with protected basenames are preserved while root-level protected entries remain excluded.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/hermes_cli/test_profile_distribution.py Adds regression tests covering root-scoped USER_OWNED_EXCLUDE behavior.
hermes_cli/profile_distribution.py Updates directory copy logic to stop ignoring protected basenames in nested directories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 558 to +567
if entry.is_dir():
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(
entry,
dest,
ignore=lambda d, names: [n for n in names if n in USER_OWNED_EXCLUDE],
)
# ``USER_OWNED_EXCLUDE`` is root-scoped: the loop above already
# filters protected root entries. Filtering by basename at every
# descendant depth (the prior ``ignore=`` lambda) silently dropped
# distribution-owned nested paths that happened to share a name
# with a root-level protected entry, e.g.
# ``skills/<category>/hermes-agent/SKILL.md``.
shutil.copytree(entry, dest)
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels May 23, 2026
@briandevans
briandevans force-pushed the fix/profile-distribution-nested-basename-31033 branch 3 times, most recently from 11e6f37 to d5bb4f9 Compare May 29, 2026 21:12
@briandevans
briandevans force-pushed the fix/profile-distribution-nested-basename-31033 branch from d5bb4f9 to b12d11c Compare June 2, 2026 22:13
`_copy_dist_payload` filtered protected basenames at every recursive
depth via `shutil.copytree(..., ignore=lambda d, names: ...)`. The outer
`for entry in staged.iterdir()` already drops root-level user-owned
entries, so the inner lambda was redundant for the protection use case
but actively wrong for distribution-owned nested paths that share a
basename with a root-level protected entry — e.g.
`skills/<category>/hermes-agent/SKILL.md`. The nested skill silently
disappeared during `hermes profile install --force` / `hermes profile
update`.

Drop the per-depth `ignore=` argument. Root-scoped exclusion stays
intact via the existing outer-loop check at the top of
`_copy_dist_payload`. The sibling export path
(`profiles.py::_default_export_ignore`) already implements the correct
"root-only vs all-depth" split — this aligns the install/update path
with that intent.

Fixes NousResearch#31033
@briandevans
briandevans force-pushed the fix/profile-distribution-nested-basename-31033 branch from b12d11c to aceb400 Compare June 5, 2026 23:15
@teknium1

Copy link
Copy Markdown
Collaborator

This appears to be implemented on current main by a separate fix.

Automated hermes-sweeper review evidence:

  • hermes_cli/profile_distribution.py:560 still skips USER_OWNED_EXCLUDE entries at the staged payload root before recursive copy.
  • hermes_cli/profile_distribution.py:576 scopes recursive copytree ignores so nested directories with protected basenames are no longer filtered out.
  • USER_OWNED_EXCLUDE includes the relevant protected names at hermes_cli/profile_distribution.py:100, including hermes-agent at line 116.
  • Regression coverage exists in tests/hermes_cli/test_profile_distribution.py:505, checking nested protected-name directories are preserved while top-level protected entries remain excluded.
  • The implementing commit is e53b74c39450d85d210ba06e69be5022278eb974 (fix(dist): stop USER_OWNED_EXCLUDE from filtering nested directories).

Thanks for the detailed root-scope analysis here; the behavior described by this PR is now present on main.

@teknium1 teknium1 closed this Jun 21, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: hermes profile install --force deletes nested distribution-owned files under skills/<category>/hermes-agent/

4 participants