JuliusBrussee/caveman · error
SKILL.md contains NUL bytes
Error message
SKILL.md contains NUL bytes
What it means
Thrown by cavemannifyImportedSkill() in the caveman CLI during `caveman skills import`: the raw bytes of SKILL.md must not contain any NUL (0x00) byte. NUL bytes indicate binary content (or a binary file with a .md extension), which cannot be a valid skill document; the CLI exits with code 2 before any parsing.
Source
Thrown at packages/cli/src/index.ts:1576
evidence_status: "unevaluated";
publication: { status: "blocked"; blockers: string[] };
};
function importedSkillSource(input: string): { root: string; file: string } {
let resolved: string;
try { resolved = realpathSync(resolve(process.cwd(), expandTilde(input))); } catch {
throw new Error(`source does not exist: ${input}`);
}
const stat = lstatSync(resolved);
const file = stat.isDirectory() ? join(resolved, SKILL_MD) : resolved;
if (!stat.isDirectory() && basename(resolved) !== SKILL_MD) throw new Error("source file must be named SKILL.md");
if (!hasFile(file)) throw new Error(`SKILL.md missing under ${resolved}`);
return { root: stat.isDirectory() ? resolved : dirname(resolved), file };
}
function cavemannifyImportedSkill(bytes: Buffer): { body: string; removedDuplicates: number; removedSeparators: number } {
if (bytes.length === 0 || bytes.length > 256 * 1024) throw new Error("SKILL.md must be 1..262144 bytes");
if (bytes.includes(0)) throw new Error("SKILL.md contains NUL bytes");
const text = bytes.toString("utf8");
if (!Buffer.from(text, "utf8").equals(bytes)) throw new Error("SKILL.md must be valid UTF-8");
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{16,}/i.test(text)) {
throw new Error("SKILL.md contains credential-shaped material");
}
const split = splitSkillMarkdown(bytes);
if (!split) throw new Error("SKILL.md needs closed YAML frontmatter");
const blocks = split.bodyText.trim().split(/\r?\n(?:[ \t]*\r?\n)+/);
const seen = new Set<string>();
const kept: string[] = [];
let removedDuplicates = 0;
let removedSeparators = 0;
for (const block of blocks) {
const clean = block.trim();
if (/^(?:---+|\*\*\*+|___+)$/.test(clean)) {
removedSeparators++;
continue;
}View on GitHub (pinned to 5184b3d11a)
Solutions
- Detect: LC_ALL=C grep -q $'\x00' SKILL.md && echo binary
- Re-save the file as UTF-8 text (from UTF-16: iconv -f UTF-16 -t UTF-8)
- If the file is truly binary, it is not a skill — restore the real markdown source
Example fix
# before: UTF-16LE file (NUL bytes) file SKILL.md # → UTF-16 Unicode text # after iconv -f UTF-16LE -t UTF-8 SKILL.md > SKILL.utf8.md && mv SKILL.utf8.md SKILL.md caveman skills import ./skills/foo
Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
const bytes = readFileSync(file);
if (bytes.includes(0)) throw new Error("SKILL.md contains NUL bytes — re-export as UTF-8 text"); Type guard
const isNulFree = (bytes: Buffer) => !bytes.includes(0);
Try / catch
try {
execFileSync("caveman", ["skills", "import", src]);
} catch (err) {
if (/NUL bytes/.test(err.stderr?.toString() ?? "")) {
// re-encode the source (iconv -f UTF-16 -t UTF-8) and retry once
}
} Prevention
- Save skill files as UTF-8 without BOM; avoid UTF-16 exports
- Never rename binary artifacts to SKILL.md
- Screen generated skills with a NUL-byte check before import
When it happens
Trigger: A SKILL.md that is actually a binary saved with the wrong extension; a file that got a NUL from a terminal copy-paste or a truncated UTF-16 export (UTF-16LE ASCII has NUL bytes interleaved); output of a tool that pads or corrupts writes.
Common situations: Windows editors saving UTF-16; a gzip/zip artifact renamed to SKILL.md; files transferred through a channel that introduces stray NULs; generated skills from a buggy script writing Buffer(0)-padded content.
Related errors
- SKILL.md must be valid UTF-8
- SKILL.md must be 1..262144 bytes
- source does not exist: ${input}
- source file must be named SKILL.md
- SKILL.md missing under ${resolved}
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/46817eaed0ede2cb.
Report an issue: GitHub.