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

  1. Confirm the coordination JSON path exists and is spelled correctly: run ls -l on the configured directory/file before invoking the tool.
  2. Validate the JSON manually (node -e "JSON.parse(require('fs').readFileSync(path,'utf8'))") to see the real parse error, since readJson masks it.
  3. If the file is corrupt or truncated, delete it and let the tooling regenerate it, or restore it from a known-good backup.
  4. 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

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


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)