affaan-m/ECC · error
Unable to read at
Error message
Unable to read ${label} at ${filePath}: ${error.message} What it means
readJsonObject() in scripts/lib/hooks-config.js reads a JSON file from disk (labelled, e.g. settings.json or the hooks metadata sidecar). If fs.readFileSync throws — file missing, permission denied, path is a directory — it rethrows as 'Unable to read <label> at <filePath>: <reason>'. This gives callers a consistent error naming the file and the underlying OS reason.
Solutions
- Create the file at <filePath> (e.g. run the setup step that initializes hooks config) or point the tool at an existing file.
- Fix file permissions so the current user can read it (chmod/chown).
- Verify the path is a file, not a directory, and that you are running from the expected project root.
- Read error.message in the thrown error for the exact OS reason (ENOENT vs EACCES vs EISDIR).
Example fix
// before
const cfg = readHooksConfig('.claude/settings.json'); // ENOENT if missing
// after
const fs = require('fs');
const path = '.claude/settings.json';
if (!fs.existsSync(path)) fs.writeFileSync(path, '{}');
const cfg = readHooksConfig(path); Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
function canReadFile(p) {
try { return fs.statSync(p).isFile() && fs.accessSync(p, fs.constants.R_OK) === undefined; }
catch { return false; }
}
if (!canReadFile('.claude/settings.json')) {
fs.writeFileSync('.claude/settings.json', '{}'); // create default before reading
} Type guard
function isReadableFile(p) { try { return fs.statSync(p).isFile(); } catch { return false; } } Try / catch
try {
const cfg = readHooksConfig(path);
} catch (err) {
if (String(err.message).startsWith('Unable to read')) {
console.error(`Cannot read ${path}: ${err.message}. Creating default.`);
fs.writeFileSync(path, '{}');
} else throw err;
} Prevention
- Run ECC tooling from the project root so relative paths resolve correctly.
- Create default config files during setup before any tool reads them.
- Check file permissions after cloning or switching users.
- Match on the 'Unable to read' prefix to distinguish read failures from parse failures.
When it happens
Trigger: Calling hooksConfig(), readHooksConfig(), or addJsonMergeOperation() where the hooks/settings JSON path does not exist, is unreadable due to permissions, is a directory, or sits on an unmounted volume.
Common situations: Running ECC hooks tooling before the project's settings.json exists; wrong working directory so relative paths resolve elsewhere; restrictive file permissions after cloning as another user; ENOENT on the metadata sidecar during first-time setup.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Failed to read
- all overlays must be readable local files
- all takes must be readable local files
- artifact byte count exceeded during reading
- artifact byte count mismatch
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/685c2bc6dfe956ac.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/hooks-config.js:32
* so every sidecar entry also carries a fingerprint of the matcher entry it
* describes. A mismatch means the two files drifted apart.
*/
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const HOOKS_FILENAME = 'hooks.json';
const METADATA_FILENAME = 'hooks.metadata.json';
const FINGERPRINT_LENGTH = 12;
const FINGERPRINT_PATTERN = /^[0-9a-f]{12}$/;
function readJsonObject(filePath, label) {
let raw;
try {
raw = fs.readFileSync(filePath, 'utf8');
} catch (error) {
throw new Error(`Unable to read ${label} at ${filePath}: ${error.message}`);
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw new Error(`Invalid JSON in ${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 metadataPathFor(hooksPath) {
return path.join(path.dirname(hooksPath), METADATA_FILENAME);View on GitHub (pinned to 8321021c54)