nexu-io/open-design · error
project_files.read_json could not parse JSON at
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.
Solutions
- Validate the file with a JSON linter (e.g. `node -e "JSON.parse(require('fs').readFileSync('f','utf8'))"`) and fix the syntax error.
- Remove comments, trailing commas, and single quotes; use strict JSON.
- 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
- Lint JSON files before committing (e.g. `node --check` or a JSON validator).
- Avoid comments, trailing commas, and single quotes in files read by read_json.
- Convert JSONC/JSON5/YAML to strict JSON before pointing read_json at them.
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
- project_files.read_json does not follow symlinks
- project_files.read_json file exceeds 256KB
- project_files.read_json only supports .json files
- project_files.read_json path escapes project dir
- project_files.read_json path must be a file
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/f2808468d5b249f2.
Report an issue: GitHub.
Appendix: 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)