affaan-m/ECC · error · Error

Failed to write

Error message

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

What it means

writeFileOrThrow wraps fs.writeFileSync for the CI catalog sync, converting write failures (EACCES, EISDIR, ENOSPC, read-only FS) into a clear Error naming the target file's basename plus the underlying Node error message. It is called when the check regenerates or patches catalog files.

Solutions

  1. Fix write permissions on the file (chown/chmod) or run as a user that owns the checkout
  2. Ensure the checkout is writable (drop :ro mounts, remount rw)
  3. Verify the path is a file, not a directory, and the parent directory exists
  4. Free disk space if the message shows ENOSPC

Example fix

// before (root-owned file)
Error: Failed to write install-components.json: EACCES...
// after
sudo chown -R $(whoami) manifests/
node scripts/ci/catalog.js fix
Defensive patterns

Strategy: try-catch

Validate before calling

const fs = require('fs');
fs.accessSync(path.dirname(filePath), fs.constants.W_OK); // throws early if dir unwritable
if (fs.existsSync(filePath)) fs.accessSync(filePath, fs.constants.W_OK);

Type guard

function isWritableFile(p) {
  try {
    const st = fs.statSync(p);
    return st.isFile() && fs.accessSync(p, fs.constants.W_OK) === undefined;
  } catch { return false; }
}

Try / catch

try {
  writeFileOrThrow(filePath, content);
} catch (err) {
  if (/^Failed to write/.test(err.message)) {
    console.error(`Cannot write ${filePath}: ${err.message}. Check permissions/read-only FS.`);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: runCatalogCheck (or fix mode) attempting to rewrite a catalog file while the path is unwritable: read-only checkout, root-owned file, target is a directory, or disk full.

Common situations: CI runners checking out code with read-only permissions; running the fixer as a non-root user against root-owned generated files; out-of-disk containers; accidentally pointing at a directory path; Docker builds with a read-only layer.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at scripts/ci/catalog.js:76

    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);
}

function parseReadmeExpectations(readmeContent) {
  const expectations = [];

  const quickStartMatch = readmeContent.match(
    /access to\s+(\d+)\s+agents,\s+(\d+)\s+skills,\s+and\s+(\d+)\s+(?:commands|legacy command shims?)/i
  );
  if (!quickStartMatch) {

View on GitHub (pinned to 8321021c54)