nexu-io/open-design · error

invalid JSON in ${filePath}: ${message}

Error message

invalid JSON in ${filePath}: ${message}

What it means

Thrown by readJsonObject() in the artifacts CLI when JSON.parse fails on the file pointed to by --manifest. The daemon never sees this; it is a local CLI-side parse guard that fires before any HTTP request is made. The original parser message is appended so the syntax error is visible.

Source

Thrown at apps/daemon/src/artifacts-cli.ts:98

      options.encoding = value;
    } else if (arg === '-h' || arg === '--help') {
      options.help = true;
    } else {
      return { error: `unknown option: ${arg}` };
    }
  }

  return options;
}

async function readJsonObject(filePath: string): Promise<JsonObject> {
  const text = await readFile(filePath, 'utf8');
  let value: unknown;
  try {
    value = JSON.parse(text) as unknown;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`invalid JSON in ${filePath}: ${message}`);
  }
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error(`${filePath} must contain a JSON object`);
  }
  return value as JsonObject;
}

export async function runArtifactsCli(args: string[]): Promise<ArtifactCliResult> {
  const options = parseOptions(args);
  if ('error' in options) return fail(options.error);
  if (options.help || !options.command) {
    process.stdout.write(USAGE);
    return { exitCode: options.command ? 0 : 1 };
  }
  if (options.command !== 'create') return fail(`unknown artifacts command: ${options.command}`);
  if (!options.name) return fail('create requires --name <path>');
  if (!options.inputPath) return fail('create requires --input <file>');

View on GitHub (pinned to 5be4028344)

Solutions

  1. Validate the file with a JSON linter or `node -e "JSON.parse(require('fs').readFileSync('<path>','utf8'))"` and fix the reported syntax position.
  2. Re-save the file as plain UTF-8 without BOM and with double-quoted keys.
  3. If you intended to allow comments/trailing commas, switch to strict JSON; the parser does not accept JSONC.
  4. Check the appended ${message} in the error — it names the exact byte offset of the parse failure.

Example fix

// before: manifest.json
{ kind: "html", renderer: "html", exports: ["html"] }

// after: valid JSON (quoted keys)
{ "kind": "html", "renderer": "html", "exports": ["html"] }
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
function assertValidManifestFile(p: string) {
  const text = readFileSync(p, 'utf8');
  let v: unknown;
  try { v = JSON.parse(text); } catch (e) {
    throw new Error(`manifest ${p} is not valid JSON: ${(e as Error).message}`);
  }
  if (!v || typeof v !== 'object' || Array.isArray(v)) {
    throw new Error(`manifest ${p} must be a JSON object`);
  }
}
assertValidManifestFile(manifestPath);

Type guard

function isJsonObject(value: unknown): value is Record<string, unknown> {
  return !!value && typeof value === 'object' && !Array.isArray(value);
}

Try / catch

try {
  const manifest = await readJsonObject(manifestPath);
} catch (e) {
  console.error((e as Error).message); // already includes the parse offset
  process.exit(1);
}

Prevention

When it happens

Trigger: Running `od artifacts create --manifest <path>` where <path> is missing a comma, has a trailing comma, an unquoted key, BOM, or any other JSON syntax error. readJsonObject reads the file as utf8 and calls JSON.parse on the contents.

Common situations: Hand-editing a manifest JSON and leaving a syntax error; saving with an editor that inserts a UTF-8 BOM; copy-pasting a manifest from docs that used single quotes; CRLF/inline comments (JSONC) mistakenly treated as JSON.

Understand the failure class

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/6e59a4d9e9db928a. Report an issue: GitHub.