fix(profiles,banner): exclude infrastructure from --clone-all + fix stale update-check repo resolution (salvage of #5025, #5026, #21728)#22475
Merged
Conversation
…lone-all When the source profile is the default (~/.hermes), shutil.copytree() was copying multi-GB infrastructure alongside the ~40 MB of actual profile data: hermes-agent/ (repo checkout + 3 GB venv), .worktrees/, profiles/ (sibling profiles — recursive!), bin/ (installed binaries), node_modules/ (hundreds of MB). Add _CLONE_ALL_DEFAULT_EXCLUDE_ROOT frozenset with these five entries and pass an ignore callback to copytree(). Exclusions are gated on the source actually being the default profile (is_default_source) so named-profile sources are never affected. Also exclude at any depth: __pycache__/, *.pyc, *.pyo, *.sock, *.tmp. Profile data (config.yaml, .env, auth.json, state.db, sessions/, skills/, logs/) is preserved intact — clone-all means 'complete snapshot minus infrastructure'. Mirrors the approach already used by _default_export_ignore() and _DEFAULT_EXPORT_EXCLUDE_ROOT (the export-side exclusion set which is broader because it produces a portable archive, not a live clone). Co-authored-by: MustafaKara7 <karamusti912@gmail.com> Co-authored-by: fahdad <30740087+fahdad@users.noreply.github.com> Fixes #5022 Based on PRs #5025, #5026, and #21728
…-scoped path check_for_updates() and _resolve_repo_dir() were preferring $HERMES_HOME/hermes-agent/ over Path(__file__).parent.parent.resolve() when looking for a .git checkout. For profiles created with --clone-all, $HERMES_HOME/hermes-agent/ points to a stale copy with a frozen HEAD, causing persistent "N commits behind" banners that never resolved. Flip the resolution order: prefer the running code's location first, fall back to $HERMES_HOME/hermes-agent/ only when the live checkout doesn't have a .git (system-wide pip installs, distro packages). The embedded-rev branch (HERMES_REVISION env var, set by nix builds) is unaffected — it uses git ls-remote against upstream, never reads the local checkout's HEAD. Based on PR #21728 by @fahdad
Contributor
🔎 Lint report:
|
This was referenced May 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hybrid salvage combining the best parts of three open PRs (#5025, #5026, #21728) that all targeted bug #5022:
--clone-allwas copying ~2.3 GB of infrastructure alongside ~40 MB of actual profile data.What this PR does
fix(profiles): exclude infrastructure artifacts when cloning with --clone-all
When the source profile is the default (
~/.hermes),shutil.copytree()was copying multi-GB infrastructure:hermes-agent/(repo checkout + 3 GB venv),.worktrees/,profiles/(sibling profiles — recursive!),bin/(installed binaries, ~10 MB),node_modules/(hundreds of MB).Fix: Add
_CLONE_ALL_DEFAULT_EXCLUDE_ROOTfrozenset with these five entries and pass anignorecallback toshutil.copytree(). Exclusions are gated on the source actually being the default profile (is_default_source) so a named-profile source is never affected.Also exclude at any depth:
__pycache__/,*.pyc,*.pyo,*.sock,*.tmp.Profile data (config.yaml, .env, auth.json, state.db, sessions/, skills/, logs/) is preserved intact — clone-all means "complete snapshot minus infrastructure", which matches what the docs promise.
Mirrors the approach already used by
_default_export_ignore()and_DEFAULT_EXPORT_EXCLUDE_ROOT(the export-side exclusion set is broader because it produces a portable archive, not a live clone).fix(banner): resolve update-check repo from running code, not profile-scoped path
check_for_updates()and_resolve_repo_dir()were preferring$HERMES_HOME/hermes-agent/overPath(__file__).parent.parent.resolve()when looking for a.gitcheckout. For profiles created with--clone-all,$HERMES_HOME/hermes-agent/points to a stale copy with a frozen HEAD, causing persistent "N commits behind" banners that never resolved.Fix: Flip the resolution order — prefer the running code's location first, fall back to
$HERMES_HOME/hermes-agent/only when the live checkout doesn't have a.git(system-wide pip installs, distro packages). The embedded-rev branch (HERMES_REVISIONenv var, set by nix builds) is unaffected — it usesgit ls-remoteagainst upstream, never reads the local checkout's HEAD.How this salvage was assembled
is_defaultgating,binin exclusion set,test_clone_all_excludes_default_infrastructurepatternprofiles-only exclusion, no.pyc/.pyocoverage_DEFAULT_EXPORT_EXCLUDE_ROOTreuse (strips state.db/logs/caches — wrong for clone-all); package.json exclusionPath.resolve()with try/except,*.pyc/*.pyoat any depth, banner.py fix as separate commitis_defaultgate), missingbinfrom exclusion set, anonymized commitsWhat was deliberately excluded (and why)
state.db,sessions/,logs/,caches,checkpoints/_DEFAULT_EXPORT_EXCLUDE_ROOT) incorrectly stripped them.is_defaultgating ensures named-profile sources are never silently stripped — a conservative choice. #21728's unconditional approach is defensible (those names don't exist in named profiles) but the gated version is more explicit about intent.Author attribution (for contributor audit)
fix(profiles): ...): donrhmexe, with Co-authored-by trailers for MustafaKara7 and fahdadfix(banner): ...): fahdad (re-authored with30740087+fahdad@users.noreply.github.com— their commits on fix(cli): resolve update-check repo from running code + exclude infra artifacts from --clone-all #21728 were anonymized ashermes@agent.local)don.rhm@gmail.com) and MustafaKara7 (karamusti912@gmail.com) are already inAUTHOR_MAPresolve_author()pattern matching — no AUTHOR_MAP edit neededTest plan
bash scripts/run_tests.sh tests/hermes_cli/test_profiles.py tests/hermes_cli/test_update_check.py tests/hermes_cli/test_banner.py→ 125 passedtest_clone_all_excludes_default_infrastructureasserts: hermes-agent, .worktrees, profiles, bin, node_modules excluded; pycache/.pyc/.pyo/.sock/.tmp excluded at any depth; profile data (skills, config, .env, state.db, sessions, logs) preservedCloses
Closes #5022, #5025, #5026, #21728.
Credit to @donrhmexe, @rahimsais, @MustafaKara7, and @fahdad — this PR combines their independent findings into a unified fix.