NousResearch/hermes-agent · error
Desktop installation ID is owned by another user.
Error message
Desktop installation ID is owned by another user.
What it means
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.
Source
Thrown at apps/desktop/electron/desktop-installation.ts:96
continue
}
try {
const winner = readInstallationId(filePath)
if (winner) {
return winner
}
try {
const stat = fs.lstatSync(filePath)
if (!stat.isFile() && !stat.isSymbolicLink()) {
throw new Error('Desktop installation ID path is not a regular file.')
}
if (!stat.isSymbolicLink() && typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
throw new Error('Desktop installation ID is owned by another user.')
}
fs.unlinkSync(filePath)
} catch (error: any) {
if (error?.code !== 'ENOENT') {
throw error
}
}
fs.writeFileSync(filePath, JSON.stringify({ installationId }), { encoding: 'utf8', flag: 'wx', mode: 0o600 })
return installationId
} finally {
if (repairFd !== undefined) {
fs.closeSync(repairFd)
}
try {View on GitHub (pinned to c896c09c42)
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.
Example fix
# before
resume_job(job_id) # one-shot time already passed while paused
# after
update_job(job_id, {"enabled": True, "state": "scheduled", "paused_at": None, "schedule": "30m"}) Defensive patterns
Strategy: try-catch
Validate before calling
from cron.jobs import compute_next_run
def resumable(job: dict) -> bool:
return compute_next_run(job["schedule"]) is not None # None => one-shot already past Try / catch
from cron.jobs import resume_job
try:
resume_job(job_id)
except ValueError as e:
if "Cannot resume" in str(e):
# give it a fresh future schedule instead of reviving the dead timestamp
update_job(job_id, {"enabled": True, "state": "scheduled", "paused_at": None, "schedule": "30m"}) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Timed out connecting to Hermes backend after ${timeoutMs}ms
- Could not generate a valid desktop installation ID.
- Desktop installation ID path is not a regular file.
- Preview mode — launching is disabled.
- no install root
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/4c3dfb6f99be0f67.
Report an issue: GitHub.