hermes - 💡(How to fix) Fix profile create --clone-all --clone-from default recurses into destination when ~/.hermes/profiles/ is non-empty [4 comments, 3 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#17743Fetched 2026-05-01 05:56:10
View on GitHub
Comments
4
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
commented ×4labeled ×3closed ×1

hermes profile create <name> --clone-all --clone-from default triggers a deep recursive copy whenever ~/.hermes/profiles/ already contains at least one profile. The new profile directory ends up with <name>/profiles/<name>/profiles/<name>/... nesting that grows to 15+ levels and many GB before something aborts the copy.

In a real instance this produced a 24 GB profile directory after a single hermes profile create invocation.

Error Message

Optionally, validate up front that profile_dir is not a descendant of source_dir and raise a clear error otherwise — that would catch any future variant of this footgun.

Root Cause

hermes profile create <name> --clone-all --clone-from default triggers a deep recursive copy whenever ~/.hermes/profiles/ already contains at least one profile. The new profile directory ends up with <name>/profiles/<name>/profiles/<name>/... nesting that grows to 15+ levels and many GB before something aborts the copy.

In a real instance this produced a 24 GB profile directory after a single hermes profile create invocation.

Fix Action

Workaround

Clone from a named sibling profile so the destination is not a child of the source:

hermes profile create <name> --clone-all --clone-from <existing-named-profile>

I built a curated `template` profile via direct rsync from `~/.hermes/` with `--exclude=/profiles` (plus a few state files like `state.db*`, `sessions/`, `.hermes_history`, `gateway.{pid,lock}`, etc.), and switched my profile-creation automation to clone from `template`. Works reliably.

Code Example

# First named profile from defaultsucceeds (see "Why" below)
hermes profile create alpha --clone-all --clone-from default

# Second named profile from default — recurses
hermes profile create beta  --clone-all --clone-from default

find ~/.hermes/profiles/beta -name profiles -type d | wc -l   # >> 1
du -sh ~/.hermes/profiles/beta                                  # multi-GB

---

if clone_all and source_dir:
    shutil.copytree(source_dir, profile_dir)

---

hermes profile create <name> --clone-all --clone-from <existing-named-profile>
RAW_BUFFERClick to expand / collapse

Summary

hermes profile create <name> --clone-all --clone-from default triggers a deep recursive copy whenever ~/.hermes/profiles/ already contains at least one profile. The new profile directory ends up with <name>/profiles/<name>/profiles/<name>/... nesting that grows to 15+ levels and many GB before something aborts the copy.

In a real instance this produced a 24 GB profile directory after a single hermes profile create invocation.

Reproduction

# First named profile from default — succeeds (see "Why" below)
hermes profile create alpha --clone-all --clone-from default

# Second named profile from default — recurses
hermes profile create beta  --clone-all --clone-from default

find ~/.hermes/profiles/beta -name profiles -type d | wc -l   # >> 1
du -sh ~/.hermes/profiles/beta                                  # multi-GB

Why

In hermes_cli/profiles.py, the --clone-all branch of create_profile calls:

if clone_all and source_dir:
    shutil.copytree(source_dir, profile_dir)

with no ignore parameter. With --clone-from default:

  • source_dir = ~/.hermes/
  • profile_dir = ~/.hermes/profiles/<name>/ (a child of source_dir)

shutil.copytree walks the source, descends into ~/.hermes/profiles/, and recurses into the destination it is currently populating.

Why the first profile escapes: shutil.copytree calls os.scandir(src) before it calls os.makedirs(dst). If ~/.hermes/profiles/ does not yet exist when the source listing is captured at the top level, it never appears in the iteration and no recursion happens. (The resulting first profile has no profiles/ subdirectory at all — easy to verify on disk.) Once ~/.hermes/profiles/ exists, every subsequent --clone-all --clone-from default recurses.

For comparison, export_profile in the same file already does the right thing — it passes a _default_export_ignore callable to shutil.copytree that excludes profiles, state.db*, .env, auth.json, and other infra when the source is the default profile. The --clone-all path has no equivalent filter.

Suggested fix

When source_dir == _get_default_hermes_home(), pass an ignore callable to shutil.copytree on the --clone-all path — at minimum excluding profiles/ at the source root. The existing _default_export_ignore is a near drop-in (or a less aggressive variant of it).

Optionally, validate up front that profile_dir is not a descendant of source_dir and raise a clear error otherwise — that would catch any future variant of this footgun.

Workaround

Clone from a named sibling profile so the destination is not a child of the source:

hermes profile create <name> --clone-all --clone-from <existing-named-profile>

I built a curated `template` profile via direct rsync from `~/.hermes/` with `--exclude=/profiles` (plus a few state files like `state.db*`, `sessions/`, `.hermes_history`, `gateway.{pid,lock}`, etc.), and switched my profile-creation automation to clone from `template`. Works reliably.

Environment

  • Hermes Agent v0.11.0 (2026.4.23)
  • Python 3.11.2
  • Debian 12 / Linux 6.1.0-44-cloud-amd64

extent analysis

TL;DR

Pass an ignore callable to shutil.copytree to exclude profiles/ at the source root when source_dir is the default Hermes home directory.

Guidance

  • Identify the create_profile function in hermes_cli/profiles.py and modify the --clone-all branch to pass an ignore callable to shutil.copytree.
  • Use the existing _default_export_ignore callable as a reference to exclude profiles/ and other infra directories.
  • Consider adding a validation check to ensure profile_dir is not a descendant of source_dir to prevent similar issues in the future.
  • As a temporary workaround, clone from a named sibling profile instead of the default profile.

Example

if clone_all and source_dir:
    ignore_callable = _default_export_ignore
    shutil.copytree(source_dir, profile_dir, ignore=ignore_callable)

Notes

The provided workaround of cloning from a named sibling profile is a reliable solution, but addressing the root cause by modifying the create_profile function will provide a more permanent fix.

Recommendation

Apply the suggested fix by passing an ignore callable to shutil.copytree to exclude profiles/ at the source root, as this will prevent the recursive copy issue and ensure a correct profile creation process.

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 profile create --clone-all --clone-from default recurses into destination when ~/.hermes/profiles/ is non-empty [4 comments, 3 participants]