affaan-m/ECC · error
Cannot read coordination JSON.
Error message
Cannot read coordination JSON.
What it means
readJson wraps readBounded + JSON.parse and deliberately collapses every failure other than the size-limit error into the generic message 'Cannot read coordination JSON.' It is thrown when the file is missing/unopenable, unreadable, or its contents are not valid JSON. The original cause is swallowed by design so callers get a stable error string, at the cost of hiding the root cause.
Solutions
- Confirm the coordination JSON path exists and is spelled correctly: run ls -l on the configured directory/file before invoking the tool.
- Validate the JSON manually (node -e "JSON.parse(require('fs').readFileSync(path,'utf8'))") to see the real parse error, since readJson masks it.
- If the file is corrupt or truncated, delete it and let the tooling regenerate it, or restore it from a known-good backup.
- Check file permissions (read access for the current user); fix with chmod/chown if a different user created the file.
Example fix
// before: opaque failure
const manifest = readJson(statePath);
// after: surface the underlying cause
catch (error) {
if (!fs.existsSync(statePath)) throw new Error(`Missing coordination state file: ${statePath}`);
console.error(error); // log real cause
throw error;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!fs.existsSync(file)) throw new Error(`Coordination state file not found: ${file}`);
const raw = fs.readFileSync(file, 'utf8');
JSON.parse(raw); // fail early with the real parse error Type guard
function isParsableObject(text) { try { const v = JSON.parse(text); return v !== null && typeof v === 'object' && !Array.isArray(v); } catch { return false; } } Try / catch
let manifest;
try {
manifest = readJson(file);
} catch (error) {
if (error.message === 'Cannot read coordination JSON.') {
// fall back to a default/empty manifest after verifying the file is absent or corrupt
manifest = defaultManifest();
} else throw error;
} Prevention
- Validate JSON files with a linter/parser after any manual edit.
- Write JSON atomically (write temp file + rename) so readers never see truncated content.
- Check file existence before invoking readJson in scripts.
- Log the underlying error (readJson swallows the cause) when debugging.
When it happens
Trigger: Calling readJson(file) when: (1) the path does not exist (ENOENT); (2) permission is denied on the file or a parent directory; (3) the file content is malformed/truncated JSON so JSON.parse throws; (4) readBounded fails for any reason other than the size limit (e.g. open errors on special files).
Common situations: Running the coordination inventory before any agent has created the coordination state file, pointing the script at the wrong directory (stale config/path), a partially-written JSON file from a crashed process, or a hand-edited JSON file with a syntax error.
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.
Related errors
- JSON artifact exceeds local size limit
- Too many task directories.
- all overlays must be readable local files
- all takes must be readable local files
- application request must be a JSON object
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/4c27e952583c973a.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/coordination-inventory.js:236
} catch { return { memory, processStatus: 'unavailable', processes: [] }; }
}
function readBounded(file, limit = MAX_BYTES) {
// Refuse symlink final components, devices and files beyond the byte budget.
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
try {
const stat = fs.fstatSync(fd);
if (!stat.isFile() || stat.size > limit) throw new Error('Input exceeds file limit.');
const buffer = Buffer.alloc(limit + 1);
let size = 0; let count;
do { count = fs.readSync(fd, buffer, size, buffer.length - size, null); size += count; } while (count && size < buffer.length);
if (size > limit) throw new Error('Input exceeds file limit.');
return { content: buffer.subarray(0,size).toString('utf8'), modifiedAt: stat.mtime.toISOString() };
} finally { fs.closeSync(fd); }
}
function readJson(file) {
try { return JSON.parse(readBounded(file).content); }
catch (error) { throw new Error(error.message === 'Input exceeds file limit.' ? error.message : 'Cannot read coordination JSON.'); }
}
function collectTaskFiles(directory) {
try {
const entries = fs.readdirSync(directory, { withFileTypes: true }).filter(e => e.isDirectory() && !e.name.startsWith('.')).sort((a,b) => a.name < b.name ? -1 : 1);
if (entries.length > 64) throw new Error('Too many task directories.');
const tasks = []; const unreadable = [];
for (const entry of entries) {
let loaded = false;
for (const name of ['STATUS.md', 'status.md']) {
try {
const data = readBounded(path.join(directory, entry.name, name), 65536);
const parsed = parseWorkerStatus(data.content);
let heartbeatAt = null;
try { if (parsed.updated) heartbeatAt = timestamp(parsed.updated); } catch { /* Unknown timestamp, not a heartbeat. */ }
tasks.push(normalizeTask({ id: entry.name, paths: [], status: parsed.state || 'unknown', heartbeatAt, statusFileModifiedAt: data.modifiedAt }));
loaded = true; break;
} catch { /* Try legacy lowercase status filename; report unreadable below. */ }
}View on GitHub (pinned to 8321021c54)