{"record":{"id":"33f9d25b9032e8e7","repo":"Hmbown/CodeWhale","slug":"configuration-and-skill-entrypoints-must-be-utf-8","errorCode":null,"errorMessage":"Configuration and skill entrypoints must be UTF-8.","messagePattern":"Configuration and skill entrypoints must be UTF-8\\.","errorType":"validation","errorClass":"ConversionError","httpStatus":null,"severity":"error","filePath":"scripts/convert-plugin.py","lineNumber":127,"sourceCode":"    path = plain_path(path)\n    info = path.stat()\n    require(stat.S_ISREG(info.st_mode) and info.st_nlink == 1, \"Only regular, non-linked source files are supported.\")\n    require(info.st_size <= limit, \"Source file exceeds the conversion size limit.\")\n    # O_NOFOLLOW protects the final component against replacement after lstat.\n    fd = os.open(path, os.O_RDONLY | getattr(os, \"O_NOFOLLOW\", 0))\n    with os.fdopen(fd, \"rb\") as source:\n        opened = os.fstat(source.fileno())\n        require((info.st_dev, info.st_ino) == (opened.st_dev, opened.st_ino), \"Source changed during conversion.\")\n        content = source.read(limit + 1)\n    require(len(content) <= limit, \"Source file exceeds the conversion size limit.\")\n    return content\n\n\ndef text_file(path):\n    try:\n        return read_file(path).decode(\"utf-8\")\n    except UnicodeError:\n        raise ConversionError(\"Configuration and skill entrypoints must be UTF-8.\") from None\n\n\ndef skill_files(path, max_files=MAX_FILES, max_bytes=MAX_BYTES):\n    source = plain_path(path)\n    entry = source / \"SKILL.md\" if source.is_dir() else source\n    require(entry.name == \"SKILL.md\" or entry.suffix == \".md\", \"Select a skill directory or Markdown skill file.\")\n    text = text_file(entry)\n    parts = re.split(r\"^---\\s*$\", text, maxsplit=2, flags=re.MULTILINE)\n    require(len(parts) == 3 and not parts[0].strip(), \"Skills need YAML frontmatter with name and description.\")\n    meta = mapping(data(parts[1]), {\"name\", \"description\", \"license\", \"compatibility\", \"metadata\",\n                                  \"disable-model-invocation\", \"user-invocable\"})\n    name = meta.get(\"name\")\n    require(isinstance(name, str) and re.fullmatch(r\"[a-z0-9]+(?:-[a-z0-9]+)*\", name)\n            and len(name) <= 64, \"Skill name must be a kebab-case identifier of at most 64 characters.\")\n    description = meta.get(\"description\")\n    require(isinstance(description, str) and description.strip(), \"Skills need a non-empty description.\")\n    require(\"---\" not in description, \"Skill description contains a delimiter the native reader cannot preserve.\")\n    require(type(meta.get(\"user-invocable\", True)) is bool and meta.get(\"user-invocable\", True),","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/scripts/convert-plugin.py#L109-L145","documentation":"Guard in the plugin converter's text_file() helper: a configuration or skill entrypoint source file failed UTF-8 decoding (invalid byte sequences). The converter requires these files to be valid UTF-8 so plugin/skill content is safe to embed and hash; a binary or wrong-encoding file is the input at fault.","triggerScenarios":"Calling text_file() (via skill_files or mcp_config) on a SKILL.md, plugin config, or MCP config file that is not UTF-8, e.g. saved as UTF-16, Latin-1, or containing binary bytes.","commonSituations":"Files edited on Windows with UTF-16 default encoding; files downloaded with wrong charset; images or binaries accidentally used as skill entrypoints.","solutions":["Convert the file to UTF-8 (e.g. iconv -f UTF-16 -t UTF-8 file > file)","Re-save the file in the editor with UTF-8 encoding","Verify the entrypoint is actually a text Markdown/config file, not a binary"],"exampleFix":"# before\niconv detect: file is UTF-16LE\n// after\niconv -f UTF-16LE -t UTF-8 SKILL.md > SKILL.md","handlingStrategy":"validation","validationCode":"raw = path.read_bytes()\nraw.decode(\"utf-8\")  # raises UnicodeDecodeError before calling the converter","typeGuard":"def is_utf8_file(p) -> bool:\n    try:\n        p.read_bytes().decode(\"utf-8\")\n        return True\n    except UnicodeDecodeError:\n        return False","tryCatchPattern":null,"preventionTips":["Save all config and Markdown files as UTF-8","Never point the converter at binary files","Normalize encodings once in CI with a lint step"],"tags":["python","encoding","utf-8"],"backgroundTag":"invalid-config-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}