{"record":{"id":"4c3dfb6f99be0f67","repo":"NousResearch/hermes-agent","slug":"desktop-installation-id-is-owned-by-another-user","errorCode":null,"errorMessage":"Desktop installation ID is owned by another user.","messagePattern":"Desktop installation ID is owned by another user\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/desktop-installation.ts","lineNumber":96,"sourceCode":"      continue\n    }\n\n    try {\n      const winner = readInstallationId(filePath)\n\n      if (winner) {\n        return winner\n      }\n\n      try {\n        const stat = fs.lstatSync(filePath)\n\n        if (!stat.isFile() && !stat.isSymbolicLink()) {\n          throw new Error('Desktop installation ID path is not a regular file.')\n        }\n\n        if (!stat.isSymbolicLink() && typeof process.getuid === 'function' && stat.uid !== process.getuid()) {\n          throw new Error('Desktop installation ID is owned by another user.')\n        }\n\n        fs.unlinkSync(filePath)\n      } catch (error: any) {\n        if (error?.code !== 'ENOENT') {\n          throw error\n        }\n      }\n\n      fs.writeFileSync(filePath, JSON.stringify({ installationId }), { encoding: 'utf8', flag: 'wx', mode: 0o600 })\n\n      return installationId\n    } finally {\n      if (repairFd !== undefined) {\n        fs.closeSync(repairFd)\n      }\n\n      try {","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/desktop-installation.ts#L78-L114","documentation":"ValueError from resume_job (cron/jobs.py:2009): resuming a paused one-shot job whose run_at is beyond the 120s grace window — compute_next_run returns None, so if resumed the job would sit scheduled forever and never fire. The error tells you up front instead of leaving a zombie job.","triggerScenarios":"pause_job(id) on a one-shot, waiting past its fire time (plus grace), then resume_job(id); also a one-shot paused minutes before firing and resumed just after.","commonSituations":"Pausing a daily report one-shot to skip a day, then resuming the next day expecting it to run; long pauses spanning the scheduled time.","solutions":["Resume with a new future schedule in one step: update_job(id, {'enabled': True, 'state': 'scheduled', 'schedule': '1h'}).","Or delete the expired job and create a fresh one-shot with a new timestamp/duration.","For recurring work, prefer interval/cron schedules — they recompute the next run from now on resume."],"exampleFix":"# before\nresume_job(job_id)  # one-shot time already passed while paused\n\n# after\nupdate_job(job_id, {\"enabled\": True, \"state\": \"scheduled\", \"paused_at\": None, \"schedule\": \"30m\"})","handlingStrategy":"try-catch","validationCode":"from cron.jobs import compute_next_run\n\ndef resumable(job: dict) -> bool:\n    return compute_next_run(job[\"schedule\"]) is not None  # None => one-shot already past","typeGuard":null,"tryCatchPattern":"from cron.jobs import resume_job\ntry:\n    resume_job(job_id)\nexcept ValueError as e:\n    if \"Cannot resume\" in str(e):\n        # give it a fresh future schedule instead of reviving the dead timestamp\n        update_job(job_id, {\"enabled\": True, \"state\": \"scheduled\", \"paused_at\": None, \"schedule\": \"30m\"})","preventionTips":["Don't pause one-shot jobs to 'skip' a run — delete and recreate with a new time instead.","Prefer recurring schedules (cron/interval) for anything that gets paused; they recompute next_run from now."],"tags":["cron","resume","one-shot","schedule"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}