{"record":{"id":"fe1b795543f5faa7","repo":"NousResearch/hermes-agent","slug":"desktop-installation-id-is-invalid","errorCode":null,"errorMessage":"Desktop installation ID is invalid.","messagePattern":"Desktop installation ID is invalid\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/desktop-installation.ts","lineNumber":127,"sourceCode":"    } finally {\n      if (repairFd !== undefined) {\n        fs.closeSync(repairFd)\n      }\n\n      try {\n        fs.unlinkSync(repairPath)\n      } catch {\n        void 0\n      }\n    }\n  }\n\n  throw new Error('Could not repair the desktop installation ID.')\n}\n\nfunction sshOwnershipId(installationId, scope) {\n  if (!INSTALLATION_ID_RE.test(String(installationId || ''))) {\n    throw new Error('Desktop installation ID is invalid.')\n  }\n\n  return crypto\n    .createHash('sha256')\n    .update(`${installationId}\\0${String(scope || '')}`)\n    .digest('hex')\n    .slice(0, 32)\n}\n\nexport { INSTALLATION_ID_RE, loadOrCreateInstallationId, parseInstallationId, readInstallationId, sshOwnershipId }\n","sourceCodeStart":109,"sourceCodeEnd":138,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/desktop-installation.ts#L109-L138","documentation":"ValueError from _validate in cron/notepad.py (the per-job key/value notepad store): job_id, after str() coercion, is empty ('' or resolves falsy). set_note/get_notes and sibling APIs require a job_id because every note is scoped to one cron job; an empty scope key would collide across jobs. Cheap, eager validation before any SQLite write.","triggerScenarios":"set_note('', 'status', 'ok'); passing a variable that was never assigned (empty string default); lookups where the job id came from a failed resolve_job_ref that returned None and was str()-coerced to 'None'... no — 'None' is truthy; the empty case is a literal '' / whitespace-less falsy value from missing input.","commonSituations":"Agent tool calls omitting the job_id argument; templates like f-string lookups where the id field is empty; code paths that create a note before the job id exists.","solutions":["Pass a real job id — obtain one from create_job's return value or `hermes cron list`.","Guard call sites: if not job_id: skip or raise before calling set_note.","Note it must be non-empty AFTER str() coercion, so also avoid whitespace-only semantics confusion — only truly empty strings trip this."],"exampleFix":"# before\nset_note(job_id=\"\", key=\"status\", value=\"ok\")\n\n# after\njob = create_job(prompt=..., schedule=\"1h\")\nset_note(job_id=job[\"id\"], key=\"status\", value=\"ok\")","handlingStrategy":"validation","validationCode":"def valid_note_ref(job_id, key) -> bool:\n    return bool(str(job_id)) and bool(key)","typeGuard":"def is_non_empty_id(v) -> bool:\n    \"\"\"True when str(v) is a usable cron job reference.\"\"\"\n    return isinstance(v, (str, int)) and bool(str(v).strip())","tryCatchPattern":"try:\n    set_note(job_id, key, value)\nexcept ValueError as e:\n    if \"job_id must be non-empty\" in str(e):\n        # resolve a real id first: job = create_job(...) or lookup via list_jobs()\n        raise","preventionTips":["Capture the id from create_job's return dict and thread it through — never synthesize or default it.","Guard tool-call boundaries where models may omit the job_id argument."],"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"}