{"record":{"id":"ca93052b4022a3e8","repo":"JuliusBrussee/caveman","slug":"skill-md-missing-under-resolved","errorCode":null,"errorMessage":"SKILL.md missing under ${resolved}","messagePattern":"SKILL\\.md missing under (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":1570,"sourceCode":"\tstop_condition: { status: string; value?: string };\n\tprompt: { bytes: number; byte_budget: number; status: string };\n\tresources: { scripts: string[]; references: string[]; assets: string[]; status: string };\n\ttransformations: { exact_duplicate_blocks_removed: number; decorative_separators_removed: number };\n\tconflicts: { status: \"needs_review\"; declared: string[] };\n\tbenchmarks: { status: \"required\"; fixtures: string[] };\n\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;","sourceCodeStart":1552,"sourceCodeEnd":1588,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L1552-L1588","documentation":"Thrown by importedSkillSource() in the caveman CLI when the import source is a DIRECTORY that does not contain a readable SKILL.md file (hasFile check fails). The directory itself resolved fine, but the required skill manifest file is absent, so the import aborts with exit code 2.","triggerScenarios":"`caveman skills import ./empty-dir`, a directory whose skill file is named skill.md/SKILL.MD instead of SKILL.md, a SKILL.md that is a dangling symlink, or one with no read permission (stat fails).","commonSituations":"Importing a cloned repo's root when the skill lives in a subdirectory (e.g. must import repo/skills/foo, not repo); case-mismatched filenames carried from case-insensitive filesystems; a partially synced/downloaded skill folder missing its main file.","solutions":["cd into the source and locate the folder that actually holds SKILL.md (find . -name SKILL.md), then import that directory","Fix the filename to exactly SKILL.md if it differs in case or spelling","Repair dangling symlinks and ensure the file is readable (chmod +r)"],"exampleFix":"# before\ncaveman skills import ./cloned-repo        # SKILL.md is at ./cloned-repo/skills/foo/SKILL.md\n\n# after\ncaveman skills import ./cloned-repo/skills/foo","handlingStrategy":"validation","validationCode":"import { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst hasSkillMd = (dir: string) => {\n  const f = join(dir, \"SKILL.md\");\n  return existsSync(f) && statSync(f).isFile();\n};","typeGuard":null,"tryCatchPattern":"try {\n  execFileSync(\"caveman\", [\"skills\", \"import\", src]);\n} catch (err) {\n  if (/SKILL\\.md missing under/.test(err.stderr?.toString() ?? \"\")) {\n    // find the real skill dir: find src -name SKILL.md, then import that dir\n  }\n}","preventionTips":["Import the deepest directory that directly contains SKILL.md, not a repo root","Verify with find . -name SKILL.md before importing unfamiliar repos","Check filename case and file readability on the source tree"],"tags":["cli","filesystem","skills-import","caveman"],"backgroundTag":"file-not-found","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}