affaan-m/ECC · error
Failed to parse at
Error message
Failed to parse ${label} at ${filePath}: ${error.message} What it means
readJsonObject reads a JSON file from disk and parses it. If fs.readFileSync succeeds but JSON.parse fails (malformed JSON, trailing commas, comments, truncated file, wrong encoding), the parse error is wrapped in this error naming the label and file path plus the underlying parse message. It separates parse failures from shape failures (error 364).
Solutions
- Fix the JSON syntax in the file at filePath — validate it with a JSON linter or `JSON.parse` in a REPL; the wrapped message names the exact parse offset.
- Restore the file from version control or regenerate it with the scaffolder.
- Check for a UTF-8 BOM or wrong encoding; re-save the file as plain UTF-8.
- If the file was truncated by a crashed process, rerun the generator to rewrite it.
Example fix
// before: settings.json with trailing comma
{ "locale": "en", }
// after
{ "locale": "en" } Defensive patterns
Strategy: validation
Validate before calling
function isParseableJsonObject(p) {
try { JSON.parse(fs.readFileSync(p, 'utf8').replace(/^\uFEFF/, '')); return true; }
catch { return false; }
} Type guard
function parsesAsObject(text) {
try { const v = JSON.parse(text); return v !== null && typeof v === 'object' && !Array.isArray(v); }
catch { return false; }
} Try / catch
try {
const cfg = readJsonObject(path, 'scaffold config');
} catch (e) {
if (e.message.startsWith('Failed to parse')) {
console.error(`Malformed JSON in ${path}; validate with a JSON linter and fix syntax.`);
return restoreFromVcs(path);
}
throw e;
} Prevention
- Validate JSON files with a linter before committing them as configs.
- Never hand-edit generated JSON without re-validating (watch for trailing commas/comments).
- Save config files as plain UTF-8 without BOM; avoid rich-text editors.
- Restore from version control rather than fixing truncated files by hand.
When it happens
Trigger: Calling readJsonObject(filePath, label) — e.g. materializeScaffoldOperation reading a scaffold/manifest JSON — when the file content is not valid JSON: syntax errors, BOM/encoding issues, truncated writes, or the file being HTML/error output instead of JSON.
Common situations: Hand-editing a scaffold or settings JSON and leaving a trailing comma or comment; a partial write from a crashed process; downloading a 404 HTML page saved as .json; smart quotes introduced by a rich-text editor.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Cannot merge ECC configuration into invalid JSON at
- config must be a JSON object
- ! : invalid meta.json, continuing with defaults ( )
- Failed to parse
- Invalid JSON in at
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/cface571f034809a.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/install/plan.js:124
return {
kind: 'copy-file',
moduleId,
sourcePath,
sourceRelativePath,
destinationPath,
strategy,
ownership: 'managed',
scaffoldOnly: false,
...(contentTransform ? { contentTransform } : {}),
};
}
function readJsonObject(filePath, label) {
let parsed;
try {
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (error) {
throw new Error(`Failed to parse ${label} at ${filePath}: ${error.message}`);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
}
return parsed;
}
function materializeClaudeSettingsOperation(sourceRoot, operation) {
const sourcePath = path.join(sourceRoot, operation.sourceRelativePath);
if (!fs.existsSync(sourcePath)) {
return [];
}
// Stable ids and descriptions live in hooks/hooks.metadata.json; readHooksConfig
// merges them back so managed settings entries keep their ids.
const hooksConfig = readHooksConfig(sourcePath, operation.sourceRelativePath);View on GitHub (pinned to 8321021c54)