NousResearch/hermes-agent · error
Unknown uninstall mode: ${mode}
Error message
Unknown uninstall mode: ${mode} What it means
ValueError from _validate in cron/notepad.py: the note key is empty. Keys are the notepad's index (per job_id), so an empty key is meaningless and rejected before touching SQLite. Enforced uniformly for set_note and any API path that goes through _validate; longer keys (> MAX_KEY_CHARS) and oversized values raise their own errors.
Source
Thrown at apps/desktop/electron/desktop-uninstall.ts:42
* the work to a detached child that waits for this app's PID to exit, runs the
* Python uninstall, then removes the app bundle — then the app quits. Same
* shape as the self-update swap-and-relaunch flow already in main.ts.
*/
import path from 'node:path'
const UNINSTALL_MODES = ['gui', 'lite', 'full']
/**
* Map an uninstall mode to the `python -m hermes_cli.uninstall` argv (after the
* python executable). Uses the dedicated lightweight module entrypoint (not
* `hermes_cli.main`) so it can run under a system Python OUTSIDE the venv that
* lite/full delete — see the Finding-3 note in buildWindowsCleanupScript.
* Throws on an unknown mode so a typo can't silently become a full wipe.
*/
function uninstallArgsForMode(mode) {
if (!UNINSTALL_MODES.includes(mode)) {
throw new Error(`Unknown uninstall mode: ${mode}`)
}
return ['-m', 'hermes_cli.uninstall', '--mode', mode]
}
/** True when `mode` removes the agent (lite/full), false for gui-only. */
function modeRemovesAgent(mode) {
return mode === 'lite' || mode === 'full'
}
/** True when `mode` removes user data (full only). */
function modeRemovesUserData(mode) {
return mode === 'full'
}
/**
* Resolve the on-disk app bundle/dir to remove for the running desktop app,
* given the path to the running executable (`process.execPath`) and platform.View on GitHub (pinned to c896c09c42)
Solutions
- Always pass a descriptive non-empty key: 'last-status', 'run-count', etc.
- Filter or default empty keys before writing: key = key or 'default'.
- Keep keys under MAX_KEY_CHARS and values under MAX_VALUE_BYTES to avoid the sibling size errors.
Example fix
# before set_note(job_id, "", "ran ok") # after set_note(job_id, "last-status", "ran ok")
Defensive patterns
Strategy: validation
Validate before calling
def valid_note_key(key: str) -> bool:
return bool(key) # add len(key) <= MAX_KEY_CHARS for full pre-validation Type guard
def is_note_key(v) -> bool:
"""True when v is a non-empty, bounded notepad key."""
from cron.notepad import MAX_KEY_CHARS
return isinstance(v, str) and 0 < len(v) <= MAX_KEY_CHARS Try / catch
try:
set_note(job_id, key, value)
except ValueError as e:
if "key must be non-empty" in str(e):
set_note(job_id, key or "misc", value) # only if a default key is meaningful; else raise
raise Prevention
- Use fixed, descriptive key constants ('last-status', 'run-count') instead of data-derived keys.
- Filter records with missing key fields before batch-writing notes.
When it happens
Trigger: set_note(job_id, '', 'value'); keys built from a variable that is empty for some loop iterations; agent-generated note writes where the model emitted a value but no key.
Common situations: Programmatic loops deriving keys from data where some records lack the key field; tool-call parameter marshalling dropping empty-string defaults; refactors that swapped the key/value argument order leaving key empty.
Related errors
- Desktop installation ID is invalid.
- Preview mode — launching is disabled.
- no install root
- Remote gateway URL is not valid: ${error.message}
- ${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/f266b95fb9b83782.
Report an issue: GitHub.