affaan-m/ECC · error · Error
Failed to read ${path.basename(filePath)}: ${error.message}
Error message
Failed to read ${path.basename(filePath)}: ${error.message} What it means
After fetching the remote URL, _fetch_import_url() inspects the Content-Type response header and only allows text-like types: anything containing 'text/', 'markdown', 'yaml', 'json', or 'octet-stream'. Any other content type is rejected to prevent importing binary, executable, or media payloads disguised as instinct files. An empty/missing Content-Type is allowed (treated as permissible).
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 01e15490f0)
Solutions
- Ensure the host serves the file with a text-like Content-Type (text/plain, text/markdown, application/json, application/yaml, or application/octet-stream).
- Verify the URL points to an actual text/markdown/yaml/json file and not a packaged binary.
- If you control the server, set the correct Content-Type header for .md/.yaml/.json files.
Example fix
# server-side fix (nginx)
# before: default_type application/octet-stream;
# after:
# types { text/markdown md; application/yaml yaml; application/json json; } Defensive patterns
Strategy: validation
Validate before calling
# Optional: HEAD the URL first to check Content-Type before fetching.
import urllib.request
req = urllib.request.Request(url, method='HEAD')
with urllib.request.urlopen(req, timeout=15) as resp:
ct = resp.headers.get('Content-Type', '')
allowed = ('text/', 'markdown', 'yaml', 'json', 'octet-stream')
if ct and not any(a in ct.lower() for a in allowed):
raise SystemExit(f'unsupported content type {ct!r} — expected a text-like type') Type guard
ALLOWED_CT = ('text/', 'markdown', 'yaml', 'json', 'octet-stream')
def content_type_allowed(ct: str) -> bool:
return not ct or any(a in ct.lower() for a in ALLOWED_CT) Try / catch
try:
content = _fetch_import_url(source)
except ValueError as e:
if 'content type' in str(e):
# surface to user: the host served a binary type for a text file
log.error('host served an unsupported content type — fix server headers or pick another URL')
raise Prevention
- Serve instinct files from a host that sets a correct text-like Content-Type.
- Do not point the importer at binary archives, images, or executables.
- If you control the server, configure explicit types for .md/.yaml/.json extensions.
When it happens
Trigger: Server returns Content-Type: application/zip, image/png, application/x-executable, video/mp4, application/xml, or application/pdf for the instinct URL.
Common situations: The file is actually binary (uploaded by mistake); a CDN/proxy serves .md with a generic binary content type; the host applies a catch-all content type for unknown extensions; a misconfigured object store returns application/x-gzip.
Related errors
- Missing value for --target
- Unknown catalog command: ${options.command}
- Failed to write ${path.basename(filePath)}: ${error.message}
- plan-canvas request path must stay on the loopback server
- unsupported plan-canvas request path: ${url.pathname}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/3090575c2e216dee.
Report an issue: GitHub.