{"record":{"id":"f266b95fb9b83782","repo":"NousResearch/hermes-agent","slug":"unknown-uninstall-mode-mode","errorCode":null,"errorMessage":"Unknown uninstall mode: ${mode}","messagePattern":"Unknown uninstall mode: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/desktop-uninstall.ts","lineNumber":42,"sourceCode":" * the work to a detached child that waits for this app's PID to exit, runs the\n * Python uninstall, then removes the app bundle — then the app quits. Same\n * shape as the self-update swap-and-relaunch flow already in main.ts.\n */\n\nimport path from 'node:path'\n\nconst UNINSTALL_MODES = ['gui', 'lite', 'full']\n\n/**\n * Map an uninstall mode to the `python -m hermes_cli.uninstall` argv (after the\n * python executable). Uses the dedicated lightweight module entrypoint (not\n * `hermes_cli.main`) so it can run under a system Python OUTSIDE the venv that\n * lite/full delete — see the Finding-3 note in buildWindowsCleanupScript.\n * Throws on an unknown mode so a typo can't silently become a full wipe.\n */\nfunction uninstallArgsForMode(mode) {\n  if (!UNINSTALL_MODES.includes(mode)) {\n    throw new Error(`Unknown uninstall mode: ${mode}`)\n  }\n\n  return ['-m', 'hermes_cli.uninstall', '--mode', mode]\n}\n\n/** True when `mode` removes the agent (lite/full), false for gui-only. */\nfunction modeRemovesAgent(mode) {\n  return mode === 'lite' || mode === 'full'\n}\n\n/** True when `mode` removes user data (full only). */\nfunction modeRemovesUserData(mode) {\n  return mode === 'full'\n}\n\n/**\n * Resolve the on-disk app bundle/dir to remove for the running desktop app,\n * given the path to the running executable (`process.execPath`) and platform.","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/desktop-uninstall.ts#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nset_note(job_id, \"\", \"ran ok\")\n\n# after\nset_note(job_id, \"last-status\", \"ran ok\")","handlingStrategy":"validation","validationCode":"def valid_note_key(key: str) -> bool:\n    return bool(key)  # add len(key) <= MAX_KEY_CHARS for full pre-validation","typeGuard":"def is_note_key(v) -> bool:\n    \"\"\"True when v is a non-empty, bounded notepad key.\"\"\"\n    from cron.notepad import MAX_KEY_CHARS\n    return isinstance(v, str) and 0 < len(v) <= MAX_KEY_CHARS","tryCatchPattern":"try:\n    set_note(job_id, key, value)\nexcept ValueError as e:\n    if \"key must be non-empty\" in str(e):\n        set_note(job_id, key or \"misc\", value)  # only if a default key is meaningful; else raise\n        raise","preventionTips":["Use fixed, descriptive key constants ('last-status', 'run-count') instead of data-derived keys.","Filter records with missing key fields before batch-writing notes."],"tags":["cron","notepad","validation","valueerror"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}