{"record":{"id":"c37843dc0e7744fb","repo":"NousResearch/hermes-agent","slug":"could-not-generate-a-valid-desktop-installation-id","errorCode":null,"errorMessage":"Could not generate a valid desktop installation ID.","messagePattern":"Could not generate a valid desktop installation ID\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/desktop-installation.ts","lineNumber":55,"sourceCode":"}\n\nfunction waitForRepair() {\n  const buffer = new SharedArrayBuffer(4)\n  Atomics.wait(new Int32Array(buffer), 0, 0, 25)\n}\n\nfunction loadOrCreateInstallationId(filePath, randomUUID = crypto.randomUUID) {\n  const existing = readInstallationId(filePath)\n\n  if (existing) {\n    return existing\n  }\n\n  fs.mkdirSync(path.dirname(filePath), { recursive: true })\n  const installationId = randomUUID().toLowerCase()\n\n  if (!INSTALLATION_ID_RE.test(installationId)) {\n    throw new Error('Could not generate a valid desktop installation ID.')\n  }\n\n  const repairPath = `${filePath}.repair.lock`\n\n  for (let attempt = 0; attempt < 40; attempt++) {\n    let repairFd\n\n    try {\n      repairFd = fs.openSync(repairPath, 'wx', 0o600)\n    } catch (error: any) {\n      if (error?.code !== 'EEXIST') {\n        throw error\n      }\n\n      const winner = readInstallationId(filePath)\n\n      if (winner) {\n        return winner","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/desktop-installation.ts#L37-L73","documentation":"ValueError from update_job (cron/jobs.py:1952): the update changed the job's schedule to a 'once' kind whose run_at is more than ONESHOT_GRACE_SECONDS (120s) in the past (compute_next_run returned None). Same guard as creation, applied through the update door so an existing job can't be pushed into a state where it would never fire.","triggerScenarios":"update_job(id, {'schedule': '2026-01-01T09:00'}) with a past timestamp; editing the schedule of a paused one-shot job whose time has since passed; clock skew between the client and scheduler host.","commonSituations":"Postponing a missed one-shot by nudging the schedule but picking an already-elapsed time; copy-pasting an old timestamp; editing on a machine whose clock/timezone differs from the gateway.","solutions":["Set the new schedule to a future timestamp or a relative duration like '45m'.","If you meant to reschedule a missed run, compute now + delta rather than reusing the old time.","Verify the scheduler host's clock/timezone if the timestamp 'looks future' to you."],"exampleFix":"# before\nupdate_job(job_id, {\"schedule\": \"2026-08-14T09:00\"})  # past\n\n# after\nupdate_job(job_id, {\"schedule\": \"2h\"})  # one-shot two hours from now","handlingStrategy":"validation","validationCode":"from datetime import datetime, timedelta\n\ndef future_schedule(s: str) -> bool:\n    if not s or s[0].isdigit() and \"T\" in s:\n        try:\n            return datetime.fromisoformat(s) > datetime.now() + timedelta(minutes=5)\n        except ValueError:\n            return False\n    return True  # durations/intervals/cron are computed from now","typeGuard":null,"tryCatchPattern":"try:\n    update_job(job_id, {\"schedule\": new_sched})\nexcept ValueError as e:\n    if \"in the past\" in str(e):\n        update_job(job_id, {\"schedule\": \"1h\"})  # reschedule relative to now\n        raise","preventionTips":["When rescheduling a missed one-shot, compute now + delta instead of reusing the old timestamp.","Combine schedule changes with enabled/state changes in one update so all guards see a consistent job."],"tags":["cron","schedule","one-shot","update","clock"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}