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
- Fix write permissions on the file (chown/chmod) or run as a user that owns the checkout
- Ensure the checkout is writable (drop :ro mounts, remount rw)
- Verify the path is a file, not a directory, and the parent directory exists
- 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
- Ensure CI checkouts are writable when running fix/sync modes
- Run generated-file fixers as the checkout's owner, not root against root-owned files
- Confirm target paths are files, not directories
- Monitor disk space in CI containers
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
- 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/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)