affaan-m/ECC · error · Error

Failed to read

Error message

Failed to read ${path.basename(filePath)}: ${error.message}

What it means

readFileOrThrow in scripts/ci/catalog.js wraps fs.readFileSync and converts ENOENT/EACCES/etc. failures into a uniform Error prefixed with the file's basename and the original Node error message. Catalog sync/check routines rely on tracked files existing and being readable.

Solutions

  1. Check the file exists at the path named in the message and create/restore it if missing
  2. Run the script from the repository root so relative paths resolve
  3. Restore the file with git (`git checkout -- <path>`) if it was deleted or renamed
  4. Fix filesystem permissions if the message shows EACCES

Example fix

// before (file missing)
Error: Failed to read install-components.json: ENOENT...
// after
git checkout -- manifests/install-components.json
node scripts/ci/catalog.js check
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require('fs');
if (!fs.existsSync(filePath)) {
  console.error(`Expected file missing: ${filePath} — restore it or run from repo root`);
  process.exit(1);
}

Type guard

function isReadableFile(p) {
  try { return fs.statSync(p).isFile() && fs.accessSync(p, fs.constants.R_OK) === undefined; } catch { return false; }
}

Try / catch

try {
  const content = readFileOrThrow(filePath);
} catch (err) {
  if (/^Failed to read/.test(err.message)) {
    console.error(`Cannot read ${filePath}: ${err.message}. Run from repo root and verify the file exists.`);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling any routine that reads a tracked catalog file (e.g. install manifests, command registry) when the file does not exist at the expected repo-relative path, or lacks read permission.

Common situations: Running the CI catalog check in a partial checkout or wrong working directory; a newly added component whose manifest file was never created; a file renamed during refactor while CI still expects the old path; permission issues on shared runners.

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


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/3090575c2e216dee. Report an issue: GitHub.

Appendix: source

Thrown at scripts/ci/catalog.js:68

function buildCatalog(root = ROOT) {
  const agents = listMatchingFiles(root, 'agents', entry => entry.isFile() && entry.name.endsWith('.md'));
  const commands = listMatchingFiles(root, 'commands', entry => entry.isFile() && entry.name.endsWith('.md'));
  const skills = listMatchingFiles(root, 'skills', entry => (
    entry.isDirectory() && fs.existsSync(path.join(root, 'skills', entry.name, 'SKILL.md'))
  )).map(skillDir => `${skillDir}/SKILL.md`);

  return {
    agents: { count: agents.length, files: agents, glob: 'agents/*.md' },
    commands: { count: commands.length, files: commands, glob: 'commands/*.md' },
    skills: { count: skills.length, files: skills, glob: 'skills/*/SKILL.md' }
  };
}

function readFileOrThrow(filePath) {
  try {
    return fs.readFileSync(filePath, 'utf8');
  } catch (error) {
    throw new Error(`Failed to read ${path.basename(filePath)}: ${error.message}`);
  }
}

function writeFileOrThrow(filePath, content) {
  try {
    fs.writeFileSync(filePath, content, 'utf8');
  } catch (error) {
    throw new Error(`Failed to write ${path.basename(filePath)}: ${error.message}`);
  }
}

function replaceOrThrow(content, regex, replacer, source) {
  if (!regex.test(content)) {
    throw new Error(`${source} is missing the expected catalog marker`);
  }

  return content.replace(regex, replacer);
}

View on GitHub (pinned to 8321021c54)