NousResearch/hermes-agent · error

${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest

Error message

${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} produced no parseable JSON payload\n${result.stdout}

What it means

ValueError from _validate_workdir (cron/jobs.py:1419): a job's workdir, after expanduser(), is not an absolute path. Because cron jobs run detached from any shell cwd, a relative path is ambiguous — the scheduler would resolve it against an arbitrary working directory. Both create_job and update_job enforce this via the same normalizer.

Source

Thrown at apps/desktop/electron/bootstrap-runner.ts:729

  // The manifest is the LAST JSON line on stdout (install.ps1 may print
  // banner / info lines first depending on Console.OutputEncoding effects).
  // Find the last line that parses as JSON with a `stages` field.
  const lines = result.stdout.split(/\r?\n/).filter(Boolean)

  for (let i = lines.length - 1; i >= 0; i--) {
    try {
      const parsed = JSON.parse(lines[i])

      if (parsed && Array.isArray(parsed.stages)) {
        return parsed
      }
    } catch {
      void 0
    }
  }

  throw new Error(
    `${isPosix ? 'install.sh --manifest' : 'install.ps1 -Manifest'} produced no parseable JSON payload\n${result.stdout}`
  )
}

// Parse the JSON result frame from a stage run. The protocol guarantees
// exactly one JSON line per stage in -Json or -Stage mode (post #27224 fix
// for the double-emit bug we addressed in the install.ps1 PR).
function parseStageResult(stdout) {
  const lines = stdout.split(/\r?\n/).filter(Boolean)

  for (let i = lines.length - 1; i >= 0; i--) {
    try {
      const parsed = JSON.parse(lines[i])

      if (parsed && typeof parsed.ok === 'boolean' && typeof parsed.stage === 'string') {
        return parsed
      }
    } catch {

View on GitHub (pinned to c896c09c42)

Solutions

  1. Pass an absolute path: create_job(..., workdir='/home/me/projects/myproject').
  2. Expand at the call site from your current cwd: str(Path('myproject').resolve()).
  3. Tilde paths like '~/proj' are accepted — expanduser() handles them.
  4. To clear the workdir on update, pass None or '' rather than '.'.

Example fix

# before
create_job(prompt="run tests", schedule="1h", workdir="myproject")

# after
from pathlib import Path
create_job(prompt="run tests", schedule="1h", workdir=str(Path("myproject").resolve()))
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def safe_workdir(w) -> str | None:
    if w is None or not str(w).strip():
        return None
    p = Path(str(w).strip()).expanduser()
    return str(p) if p.is_absolute() else str(p.resolve())  # make absolute up front

Try / catch

try:
    create_job(prompt=p, schedule=s, workdir=w)
except ValueError as e:
    if "must be an absolute path" in str(e):
        create_job(prompt=p, schedule=s, workdir=str(Path(w).resolve()))  # retry absolute
        raise

Prevention

When it happens

Trigger: Calling create_job(workdir='myproject') or update_job(job_id, {'workdir': './src'}); also '~/proj' is fine (expanduser runs first) but 'proj', './proj', '../proj' are rejected.

Common situations: An agent or script passes a path relative to the conversation cwd; a user copies a relative path out of docs; the gateway's terminal.cwd config is relative and someone mirrored it into a cron job.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/dd9411bd1900f995. Report an issue: GitHub.