affaan-m/ECC · error

Failed to read

Error message

Failed to read ${label}: ${error.message}

What it means

readJson in scripts/lib/install-state.js wraps every failure of fs.readFileSync + JSON.parse of an install-state file (e.g. .claude/install-state.json) into a single uniform error. It conflates file-not-found, permission errors, and malformed JSON into one message, preserving the underlying Node error text.

Solutions

  1. Check the underlying error in the message: ENOENT means the file is missing (run the install once to create it)
  2. If the file exists, validate it with `node -e "JSON.parse(require('fs').readFileSync(path,'utf8'))"` and fix the syntax error
  3. Fix file permissions if the message shows EACCES
  4. If the state file is corrupt, delete it and re-run the installer to regenerate a fresh state

Example fix

// before: reading state from a script without guarding
const state = state();
// after
let state;
try { state = state(); } catch (e) {
  if (/ENOENT/.test(e.message)) state = undefined; // first run, no state yet
  else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check before reading state
const statePath = '.claude/install-state.json';
const stateExists = fs.existsSync(statePath);
const stateReadable = stateExists && (() => { try { fs.accessSync(statePath, fs.constants.R_OK); return true; } catch { return false; } })();
let stateWellFormed = false;
if (stateReadable) { try { JSON.parse(fs.readFileSync(statePath, 'utf8')); stateWellFormed = true; } catch {} }

Try / catch

try {
  const state = readInstallState();
} catch (e) {
  if (/Failed to read .*: ENOENT/.test(e.message)) {
    // first run — treat as fresh install
  } else if (/Failed to read .*: Unexpected token|JSON/.test(e.message)) {
    // corrupt file — regenerate state
  } else throw e;
}

Prevention

When it happens

Trigger: readInstallState/state is called and the state file is missing, unreadable (permissions), or contains invalid JSON — any readFileSync or JSON.parse throw is caught and re-thrown with the label prefixed.

Common situations: Running installer commands before the first install (state file does not exist yet); a crashed previous install left a truncated/partial state file; hand-editing the state JSON introduced a syntax error; restrictive file permissions after cloning on another machine.

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/ae2106a2161df953. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install-state.js:29

// bytes must be the installed bytes). install-state is validated by the
// hand-rolled validator below, which enforces the same constraints as
// schemas/install-state.schema.json (ecc.install.v1).

let cachedValidator = null;

function cloneJsonValue(value) {
  if (value === undefined) {
    return undefined;
  }

  return JSON.parse(JSON.stringify(value));
}

function readJson(filePath, label) {
  try {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch (error) {
    throw new Error(`Failed to read ${label}: ${error.message}`);
  }
}

function getValidator() {
  if (cachedValidator) {
    return cachedValidator;
  }

  cachedValidator = createFallbackValidator();
  return cachedValidator;
}

function createFallbackValidator() {
  const validate = state => {
    const errors = [];
    validate.errors = errors;

    function pushError(instancePath, message) {

View on GitHub (pinned to 8321021c54)