nexu-io/open-design · error
project_files.read_json path must be a file
Error message
project_files.read_json path must be a file
What it means
executeProjectFilesReadJson (refresh.ts:597-598) stats the resolved real target and requires it to be a regular file (entryStat.isFile()). A directory, device, socket, or other non-file entry is rejected because the tool can only JSON.parse file contents.
Source
Thrown at apps/daemon/src/live-artifacts/refresh.ts:598
matches.push(result);
}
return asBoundedRefreshOutput({ toolName: 'project_files.search', query: query ?? '', count: matches.length, truncated: allFiles.length > matches.length && matches.length >= maxResults, matches });
}
async function executeProjectFilesReadJson(options: ExecuteLocalDaemonRefreshSourceOptions): Promise<BoundedJsonObject> {
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 });View on GitHub (pinned to 5be4028344)
Solutions
- Point input.path at a specific .json file, not a directory.
- Remove any trailing slash from the path.
- Verify the path is a file before scheduling a refresh.
Example fix
// before
const input = { path: 'src/' };
// throws `project_files.read_json path must be a file`
// after
const input = { path: 'src/data.json' }; Defensive patterns
Strategy: validation
Validate before calling
import { stat } from 'node:fs/promises';
async function assertIsFile(target: string): Promise<void> {
const st = await stat(target);
if (!st.isFile()) throw new Error('project_files.read_json path must be a file');
} Prevention
- Point read_json at a specific file, never a directory.
- Strip trailing slashes from input.path.
- Validate the path resolves to a regular file before scheduling refresh.
When it happens
Trigger: input.path resolves to a directory, a FIFO/socket, or a device special file inside the project.
Common situations: Pointing read_json at a directory path (e.g. 'src' instead of 'src/data.json'); a path with a trailing slash; an empty extension that resolved to a folder.
Related errors
- project_files.read_json requires input.path
- project_files.read_json only supports .json files
- project_files.read_json does not follow symlinks
- project_files.read_json path escapes project dir
- project_files.read_json file exceeds 256KB
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/b95efb46c2d8d278.
Report an issue: GitHub.