{"record":{"id":"46817eaed0ede2cb","repo":"JuliusBrussee/caveman","slug":"skill-md-contains-nul-bytes","errorCode":null,"errorMessage":"SKILL.md contains NUL bytes","messagePattern":"SKILL\\.md contains NUL bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":1586,"sourceCode":"\tevidence_status: \"unevaluated\";\n\tpublication: { status: \"blocked\"; blockers: string[] };\n};\n\nfunction importedSkillSource(input: string): { root: string; file: string } {\n\tlet resolved: string;\n\ttry { resolved = realpathSync(resolve(process.cwd(), expandTilde(input))); } catch {\n\t\tthrow new Error(`source does not exist: ${input}`);\n\t}\n\tconst stat = lstatSync(resolved);\n\tconst file = stat.isDirectory() ? join(resolved, SKILL_MD) : resolved;\n\tif (!stat.isDirectory() && basename(resolved) !== SKILL_MD) throw new Error(\"source file must be named SKILL.md\");\n\tif (!hasFile(file)) throw new Error(`SKILL.md missing under ${resolved}`);\n\treturn { root: stat.isDirectory() ? resolved : dirname(resolved), file };\n}\n\nfunction cavemannifyImportedSkill(bytes: Buffer): { body: string; removedDuplicates: number; removedSeparators: number } {\n\tif (bytes.length === 0 || bytes.length > 256 * 1024) throw new Error(\"SKILL.md must be 1..262144 bytes\");\n\tif (bytes.includes(0)) throw new Error(\"SKILL.md contains NUL bytes\");\n\tconst text = bytes.toString(\"utf8\");\n\tif (!Buffer.from(text, \"utf8\").equals(bytes)) throw new Error(\"SKILL.md must be valid UTF-8\");\n\tif (/-----BEGIN [A-Z ]*PRIVATE KEY-----|\\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{16,}/i.test(text)) {\n\t\tthrow new Error(\"SKILL.md contains credential-shaped material\");\n\t}\n\tconst split = splitSkillMarkdown(bytes);\n\tif (!split) throw new Error(\"SKILL.md needs closed YAML frontmatter\");\n\tconst blocks = split.bodyText.trim().split(/\\r?\\n(?:[ \\t]*\\r?\\n)+/);\n\tconst seen = new Set<string>();\n\tconst kept: string[] = [];\n\tlet removedDuplicates = 0;\n\tlet removedSeparators = 0;\n\tfor (const block of blocks) {\n\t\tconst clean = block.trim();\n\t\tif (/^(?:---+|\\*\\*\\*+|___+)$/.test(clean)) {\n\t\t\tremovedSeparators++;\n\t\t\tcontinue;\n\t\t}","sourceCodeStart":1568,"sourceCodeEnd":1604,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L1568-L1604","documentation":"During skill import, cavemannifyImportedSkill reads SKILL.md as raw bytes and rejects any file containing NUL (0x00) bytes. Markdown skills must be clean text; NUL bytes indicate binary corruption or a wrong file. The check runs before UTF-8 decoding, so the file is rejected early.","triggerScenarios":"Calling the skill import command with a SKILL.md whose bytes include 0x00 — e.g. a file produced by a binary write, a truncated download, or a UTF-16 encoded file (which interleaves NUL bytes).","commonSituations":"Importing a skill file saved by a text editor in UTF-16; copying a skill out of a zip/tar with corruption; accidentally pointing the importer at a binary file named SKILL.md.","solutions":["Re-encode the file as UTF-8 without BOM (e.g. iconv -f UTF-16 -t UTF-8 SKILL.md) and re-import.","Re-download or re-copy the SKILL.md from its original source to fix corruption.","Verify with `grep -c $'\\0' SKILL.md` or `file SKILL.md` that the file is plain text before importing."],"exampleFix":"// before (UTF-16 encoded file)\n$ cave skill import ./skill-dir\nError: SKILL.md contains NUL bytes\n\n// after\n$ iconv -f UTF-16 -t UTF-8 SKILL.md > SKILL.md.utf8 && mv SKILL.md.utf8 SKILL.md\n$ cave skill import ./skill-dir","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'node:fs';\nconst bytes = readFileSync('SKILL.md');\nif (bytes.includes(0)) throw new Error('file contains NUL bytes; re-encode as UTF-8');","typeGuard":"const isCleanText = (b: Buffer) => b.length > 0 && !b.includes(0);","tryCatchPattern":null,"preventionTips":["Always save SKILL.md as UTF-8 (never UTF-16) in your editor","Check `file SKILL.md` shows ASCII/UTF-8 text before importing","Re-download skill files rather than copying from archives you didn't create"],"tags":["validation","encoding","cli"],"backgroundTag":"invalid-argument-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}