vercel-labs/agent-skills · error · Error

Could not read ${label} JSON at ${path}: ${err.message}

Error message

Could not read ${label} JSON at ${path}: ${err.message}

What it means

Thrown by readJson() when either readFile or JSON.parse throws for the given path/label. It wraps the underlying error message, so the detail reveals whether the file was missing (ENOENT), unreadable (permissions), or present but not valid JSON (SyntaxError). This is the single chokepoint for loading both signals.json and codebase.json.

Source

Thrown at skills/vercel-optimize/scripts/merge-signals.mjs:160

function parseArgs(argv) {
  const out = { positional: [], force: false };
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === '--out') out.outPath = argv[++i];
    else if (a.startsWith('--out=')) out.outPath = a.slice('--out='.length);
    else if (a === '--force') out.force = true;
    else out.positional.push(a);
  }
  out.signalsPath = out.positional[0];
  out.codebasePath = out.positional[1];
  return out;
}

async function readJson(path, label) {
  try {
    return JSON.parse(await readFile(path, 'utf-8'));
  } catch (err) {
    throw new Error(`Could not read ${label} JSON at ${path}: ${err.message}`);
  }
}

function assertObject(value, label) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error(`${label} must be a JSON object.`);
  }
}

async function writeOutput(path, body, { force }) {
  if (!force && await exists(path)) {
    throw new Error(`output file already exists: ${path}. Use a fresh run directory or pass --force to overwrite.`);
  }
  await mkdir(dirname(path), { recursive: true });
  await writeFile(path, body);
}

async function exists(path) {

View on GitHub (pinned to b8caa260a4)

Solutions

  1. Confirm the path exists and is readable: `ls -l <path>` and `node -e 'JSON.parse(require("fs").readFileSync("<path>","utf8"))'`.
  2. If the file is truncated/malformed, rerun the step that produced it (collect-signals or scan-codebase).
  3. Run merge-signals from the directory containing the outputs, or pass absolute paths.

Example fix

// before — readJson surfaces an opaque wrapped error
throw new Error(`Could not read ${label} JSON at ${path}: ${err.message}`);

// after — validate existence + parseability with a clear precondition
import { readFileSync, existsSync } from 'node:fs';
function assertReadableJson(path, label) {
  if (!existsSync(path)) throw new Error(`${label} not found at ${path}`);
  try { JSON.parse(readFileSync(path, 'utf-8')); }
  catch (e) { throw new Error(`${label} at ${path} is not valid JSON: ${e.message}`); }
}
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync } from 'node:fs';
import { readFileSync } from 'node:fs';
function assertJsonReadable(path, label) {
  if (!existsSync(path)) throw new Error(`${label} not found at ${path}`);
  try { JSON.parse(readFileSync(path, 'utf-8')); }
  catch (e) { throw new Error(`${label} at ${path} is not valid JSON: ${e.message}`); }
}
assertJsonReadable(signalsPath, 'signals');

Try / catch

let parsed;
try {
  parsed = await readJson(path, label);
} catch (err) {
  if (err.message.startsWith(`Could not read ${label}`)) {
    // distinguish missing vs malformed from err.message, then rerun producer or fix path
    throw new Error(`Cannot load ${label}; fix the path or regenerate the file.`);
  }
  throw err;
}

Prevention

When it happens

Trigger: The signals or codebase path does not exist (typo, wrong working directory); the file exists but contains malformed JSON (truncated write, trailing comma, BOM); permission denied on the file.

Common situations: Running merge-signals from a different cwd than where outputs were written; a previous collect/scan step crashed mid-write leaving a partial JSON file; path constructed with a wrong variable.

Related errors


AI-assisted analysis of vercel-labs/agent-skills@b8caa260a4 (2026-08-13). Data as JSON: /api/errors/d7a512f0bde721b5. Report an issue: GitHub.