nexu-io/open-design · error

project_files.read_json could not parse JSON at ${filePath}

Error message

project_files.read_json could not parse JSON at ${filePath}

What it means

executeProjectFilesReadJson (refresh.ts:601-606) reads the file and calls JSON.parse on its UTF-8 contents; if parse throws (SyntaxError), the error is wrapped with the file path so the caller knows which file failed. The catch is broad, so any parse error surfaces here.

Source

Thrown at apps/daemon/src/live-artifacts/refresh.ts:605

  const filePath = selectJsonPath(options.source.input as ProjectFilesReadJsonInput);
  if (!filePath.endsWith('.json')) throw new Error('project_files.read_json only supports .json files');
  const dir = projectDir(options.projectsRoot, options.projectId);
  const target = path.resolve(dir, filePath);
  const [dirReal, targetLinkStat] = await Promise.all([realpath(dir), lstat(target)]);
  if (targetLinkStat.isSymbolicLink()) throw new Error('project_files.read_json does not follow symlinks');
  const targetReal = await realpath(target);
  if (!targetReal.startsWith(`${dirReal}${path.sep}`) && targetReal !== dirReal) {
    throw new Error('project_files.read_json path escapes project dir');
  }
  const entryStat = await stat(targetReal);
  if (!entryStat.isFile()) throw new Error('project_files.read_json path must be a file');
  if (entryStat.size > 256 * 1024) throw new Error('project_files.read_json file exceeds 256KB');
  if (options.signal?.aborted === true) throw options.signal.reason;
  let parsed: BoundedJsonValue;
  try {
    parsed = JSON.parse(await readFile(targetReal, 'utf8')) as BoundedJsonValue;
  } catch {
    throw new Error(`project_files.read_json could not parse JSON at ${filePath}`);
  }
  return asBoundedRefreshOutput({ toolName: 'project_files.read_json', path: filePath, size: entryStat.size, json: parsed });
}

function compactExecOutput(value: string): string[] {
  return value.split('\n').map((line) => line.trimEnd()).filter(Boolean).slice(0, 100);
}

async function runGit(projectPath: string, args: string[], signal: AbortSignal | undefined): Promise<string> {
  try {
    const result = await execFileAsync('git', args, { cwd: projectPath, signal, timeout: 10_000, maxBuffer: 128 * 1024 });
    return result.stdout.toString();
  } catch (error) {
    const maybeError = error as { stdout?: string | Buffer; stderr?: string | Buffer; message?: string; code?: unknown };
    if (maybeError.code === 128) return '';
    throw new Error(maybeError.stderr?.toString().trim() || maybeError.message || 'git command failed');
  }
}

View on GitHub (pinned to 5be4028344)

Solutions

  1. Validate the file with a JSON linter (e.g. `node -e "JSON.parse(require('fs').readFileSync('f','utf8'))"`) and fix the syntax error.
  2. Remove comments, trailing commas, and single quotes; use strict JSON.
  3. If you need JSONC/JSON5, convert to plain JSON before pointing read_json at it.

Example fix

// before: data.json contains
// {
//   "total": 1,   // the count
//   "items": [],
// }
// throws `project_files.read_json could not parse JSON at data.json`

// after: valid JSON (no comments, no trailing comma)
// { "total": 1, "items": [] }
Defensive patterns

Strategy: validation

Validate before calling

import { readFile } from 'node:fs/promises';
async function assertValidJson(target: string): Promise<void> {
  try {
    JSON.parse(await readFile(target, 'utf8')) as unknown;
  } catch {
    throw new Error(`project_files.read_json could not parse JSON at ${target}`);
  }
}

Try / catch

try {
  await refreshLiveArtifact(opts);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('project_files.read_json could not parse JSON')) {
    // prompt the user to fix the file's JSON syntax
  }
  throw err;
}

Prevention

When it happens

Trigger: The target .json file exists, is under 256 KiB, is inside the project, but its contents are not valid JSON (e.g. trailing comma, single quotes, comments, JSONC/JSON5 syntax, BOM + corrupted bytes, truncated file).

Common situations: JSON-with-comments files (.jsonc) read as strict JSON; hand-edited JSON with a trailing comma; a truncated/corrupted file from an interrupted write; a file that is actually JSON5 or YAML.

Related errors


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