11 KiB
name, description, version, author, license, platforms, metadata
| name | description | version | author | license | platforms | metadata | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hermes-source-updates | Use when hermes update stashes source or cleanup is needed. | 1.1.0 | Hermes Agent | MIT |
|
|
Hermes Source Updates & Autostash Cleanup
Git-installed Hermes lives at ~/.hermes/hermes-agent. hermes update pulls that repo. Uncommitted source edits get auto-stashed first.
What "local changes are stashed" means
- Is: temporary git stash of edits inside the Hermes source tree so
git pullcan run. - Is not: config,
.env, skills, sessions, memory, profiles, cron. Those live under~/.hermes/outside the git tree and are covered by the update's pre-update snapshot, not by stash messages.
Docs: https://hermes-agent.nousresearch.com/docs/getting-started/updating/
Interactive update: stash → pull → prompt to restore.
Non-interactive (/update, desktop, --yes): updates.non_interactive_local_changes = stash (default, auto-restore) or discard.
Inspect stashes (always start here)
cd ~/.hermes/hermes-agent
git status -sb
git stash list
git stash show --stat 'stash@{N}'
git stash show -p 'stash@{N}' | head -200
Autostash names look like: hermes-update-autostash-YYYYMMDD-HHMMSS.
Noise vs keep
| Signal | Treat as |
|---|---|
Only package-lock.json / "peer": true churn |
Noise → drop |
| WIP name + lockfile-only | Noise → drop |
Real .py / SOUL.md / gateway / voice patches |
Keep until proven obsolete |
| Mixed lockfile + real code | Keep whole stash (do not surgical-extract unless user asks) |
Confirm identity before drop:
git stash list
git stash show --stat 'stash@{N}' # must match expected noise
git stash drop 'stash@{N}'
After any drop, indexes renumber. Re-run git stash list before the next drop.
Is an old stash already on main?
Do not use git diff main stash@{N} for this. Old stash commits diverge from current main across the whole tree (thousands of files) and look "non-empty" even when the patch intent is gone or unrelated.
Use the patch of the stash:
git stash show -p 'stash@{N}' > /tmp/stashN.patch
# Optional: strip lockfiles before checks
python3 - <<'PY'
from pathlib import Path
p = Path('/tmp/stashN.patch').read_text(errors='replace')
parts = p.split('diff --git ')
keep = []
for part in parts:
if not part.strip():
continue
head = part.split('\n', 1)[0]
if 'package-lock.json' in head:
continue
keep.append('diff --git ' + part)
Path('/tmp/stashN-code.patch').write_text(''.join(keep))
print('wrote code-only patch')
PY
cd ~/.hermes/hermes-agent
git apply --reverse --check /tmp/stashN-code.patch # exit 0 → content already in tree
git apply --check /tmp/stashN-code.patch # exit 0 → clean apply on current tree
Interpretation:
- reverse-check succeeds → patch content already present → safe to drop as obsolete
- reverse and forward both fail → tree drifted; patch is unique or outdated form → keep unless user chooses discard
- forward succeeds → still local-only; keep or re-apply intentionally
Also grep current tree for distinctive symbols from the stash.
Safe cleanup workflow
- List +
--statevery stash. - Drop pure lockfile/WIP noise only (confirm
--statfirst). - Hold intentional patches (SOUL, hang fixes, TTS timeouts, delegate/SearXNG local work).
- For mixed/old stashes: code-only patch check above; only drop if reverse-check proves already upstream.
- Verify:
git stash list+git status -s(expect clean aside from known untracked likebuild/).
When user says "ask Claude to clean up" / analyze stashes
Claude on 10.0.0.28 has no automatic access to Hermes-MAIN. Two modes:
- Paste mode (default): inventory (
stash list,--stat, short-p) into ask-claude. Ask for keep/drop + exact commands. You execute locally. - SSH mode (only when user says Claude may SSH in): export patches on Hermes-MAIN, authorize Claude host key if needed, then let Claude inspect live tree.
# On Hermes-MAIN (10.0.0.42) — export for Claude
mkdir -p /tmp/hermes-stashes
cd ~/.hermes/hermes-agent
for i in 0 1 2; do
git stash show -p "stash@{$i}" > "/tmp/hermes-stashes/stash${i}.patch" 2>/dev/null || true
git stash show --stat "stash@{$i}" > "/tmp/hermes-stashes/stash${i}.stat" 2>/dev/null || true
done
# From 10.0.0.28 (Claude host key is vera-ai):
ssh -i ~/.ssh/vera-ai -o StrictHostKeyChecking=accept-new [email protected] \
'git -C /home/n8n/.hermes/hermes-agent stash list; ls /tmp/hermes-stashes/'
If Permission denied, append vera-ai.pub to Hermes-MAIN ~/.ssh/authorized_keys (user must allow). Tell Claude: analysis only unless operator authorized apply/drop.
Detail: references/update-stash-cleanup.md.
Re-apply intentional stashes (after Claude KEEP+RE-APPLY)
Do not git stash pop blindly. Prefer git stash apply 'stash@{N}', resolve conflicts, leave as unstaged working-tree mods (so next hermes update will stash them again intentionally).
Pre-flight: is the intent already upstream?
Before fighting conflicts, grep current main for the behavior, not the old symbol names:
| Old stash intent | Upstream equivalent (check first) |
|---|---|
inline _DaemonThreadPoolExecutor in tool_executor.py |
tools/daemon_pool.py → DaemonThreadPoolExecutor |
gateway/run.py _voice_mode_getter wire |
often already near _sync_voice_mode_state_to_adapter |
old SearXNG in web_tools.py + refresh_delegate_schema |
newer SearXNG provider + MCP inherit helpers — usually DISCARD |
load_soul_identity=True on child agents |
kwarg may be gone — keep stash for intent, do not blind-apply |
rg -n "DaemonThreadPoolExecutor|daemon_pool" agent/tool_executor.py tools/daemon_pool.py
rg -n "_voice_mode_getter" gateway/run.py gateway/platforms/base.py
rg -n "load_soul_identity" -g '*.py'
When git stash apply fails (heavy upstream drift)
If git stash apply produces conflicts on most files, do NOT fight through
merge markers. Extract the patch, inspect each file's changes, check what's
already upstream, and apply only the missing pieces fresh with the patch tool:
# 1. Extract the full patch
git stash show -p 'stash@{N}' > /tmp/stash.patch
# 2. Split per-file and inspect each change
python3 -c "
patch = open('/tmp/stash.patch').read()
parts = patch.split('diff --git ')
for part in parts:
if not part.strip():
continue
head = part.split('\n', 1)[0]
f = head.split()[-1].replace('b/', '')
print(f'=== {f} ===')
for line in part.split('\n'):
if line.startswith('+') and not line.startswith('+++'):
print(line)
elif line.startswith('-') and not line.startswith('---'):
print(line)
print()
"
# 3. For each file, grep current tree to see what's already upstream
# (e.g. grep for the behavior, not the exact old line numbers)
# 4. Apply only the missing changes with the patch tool
# (patch mode='replace' on each file, not git stash apply)
# 5. Drop the stash once all changes are applied
git stash drop 'stash@{N}'
This avoids merge conflicts entirely and produces a clean working tree with only the intentional mods that aren't already upstream.
Conflict resolution pattern (Jul 2026) — for light drift
When git stash apply succeeds with only a few conflicts:
git stash apply 'stash@{N}'(not pop).- For each UU file:
- Upstream already has equivalent → take upstream.
- Stash has unique behavior on moved upstream code → merge (e.g. keep
_final_delivery_adapter+ addand not _voice_only). - Stash hunk landed in wrong method after drift → discard stash side; confirm real wire elsewhere.
- Clear all conflict markers.
python3 -m py_compileon every touched.py.git restore --staged .so changes are plain local mods.- Only then
git stash drop 'stash@{N}'. - Gateway/CLI restart for gateway/voice/SOUL runtime paths.
Intentional local mods on this install (post 2026-07-25)
docker/SOUL.md— custom Primary Directive SOULcli.py,hermes_cli/voice.py,tools/tts_tool.py— TTS 600s + Kokororesponse.contenttools/transcription_tools.py—.ogahermes_cli/cli_agent_setup_mixin.py— model alias routinggateway/platforms/base.py— voice_only text suppression merged with current delivery path
Not re-applied: hang-fix class (upstream daemon_pool), obsolete April SearXNG/delegate rewrite, broken load_soul_identity one-liner.
Cross-skill: voice patches also under voice-systems.
Pitfalls
- Confusing stashed source edits with missing config/skills after update.
- Dropping stashes by old index numbers after a prior drop (renumbering).
- Using whole-tree
git diff main stash@{N}to decide obsolescence — false positives. - Re-applying ancient stashes blind — inspect + selective restore only with user direction.
- Re-applying hang-fix / SearXNG stashes that regress newer upstream implementations.
- Leaving merge conflict markers or staged half-applies; always py_compile + unstage.
- Forgetting gateway restart after applying gateway/voice/SOUL source patches.
- Any running gateway process holds stale Python modules in
sys.modulesafterhermes updatetouches source on disk — not just for stash re-applies. Symptom: cron jobs (or any code path) that were working fine suddenly throwImportError: cannot import name 'X' from 'module'even though the symbol clearly exists when you read the file. This isn't specific to manual patch re-application — a plainhermes updatewhile a gateway is running for that profile can trigger it too. Fix:hermes -p <profile> gateway restartfor every profile with a live gateway after any source update, not just the one you're actively debugging. Verify viastat -c '%y' <file>vssystemctl --user show <service> -p ExecMainStartTimestamp(orps -o lstart -p <pid>) — if the file mtime is after the process start time, the running process is stale. Seehermes-cron-managementskill's stale-gateway-module-cache pitfall for the full diagnostic recipe and a worked cron-job example. - Bundled
hermes-agentskill is protected; put install/update ops learnings here, not there. - Empty stashes (0 lines). When
hermes updateruns and there are no local changes to stash, it may still create an empty autostash.git stash show --statshows nothing. These are pure noise — drop immediately.
Quick user-facing explanation
Hermes is a git checkout under
~/.hermes/hermes-agent. Update stashes uncommitted source edits so pull can succeed. Your profiles/config are separate. Stashes namedhermes-update-autostash-*are those parked edits.