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

  1. Ensure the host serves the file with a text-like Content-Type (text/plain, text/markdown, application/json, application/yaml, or application/octet-stream).
  2. Verify the URL points to an actual text/markdown/yaml/json file and not a packaged binary.
  3. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/3090575c2e216dee. Report an issue: GitHub.