{"record":{"id":"fec6930adb318c9a","repo":"jackwener/OpenCLI","slug":"skill-path-must-be-non-empty","errorCode":null,"errorMessage":"Skill path must be non-empty.","messagePattern":"Skill path must be non-empty\\.","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"src/skills.ts","lineNumber":104,"sourceCode":"function parseSkillTarget(target: string, relpath: string): { name: string; pathInSkill: string } {\n  const normalizedTarget = normalizeSkillPath(target);\n  if (relpath) {\n    return { name: normalizedTarget, pathInSkill: relpath };\n  }\n  const slash = normalizedTarget.indexOf('/');\n  if (slash === -1) {\n    return { name: normalizedTarget, pathInSkill: '' };\n  }\n  return {\n    name: normalizedTarget.slice(0, slash),\n    pathInSkill: normalizedTarget.slice(slash + 1),\n  };\n}\n\nfunction normalizeSkillPath(raw: string): string {\n  const normalized = raw.trim().replace(/\\\\/g, '/');\n  if (!normalized || normalized.includes('\\0')) {\n    throw new ArgumentError('Skill path must be non-empty.');\n  }\n  if (normalized.startsWith('/') || normalized.split('/').some((part) => part === '..')) {\n    throw new ArgumentError(`Invalid skill path: ${raw}`, 'Use a path relative to an OpenCLI skill directory.');\n  }\n  return path.posix.normalize(normalized);\n}\n\nfunction parseFrontmatter(content: string): SkillFrontmatter {\n  if (!content.startsWith('---\\n')) return {};\n  const end = content.indexOf('\\n---', 4);\n  if (end < 0) return {};\n  try {\n    const parsed = yaml.load(content.slice(4, end));\n    return parsed && typeof parsed === 'object' ? parsed as SkillFrontmatter : {};\n  } catch {\n    return parseLooseFrontmatter(content.slice(4, end));\n  }\n}","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/skills.ts#L86-L122","documentation":"normalizeSkillPath trims the raw input, converts backslashes to slashes, and rejects empty strings and NUL bytes before any path resolution. An empty/whitespace-only path (or one containing '\\0') means there is nothing valid to normalize, so it throws ArgumentError (usage error, exit code 2).","triggerScenarios":"Calling the skills read path with '' or a whitespace-only string for the in-skill path, or a path containing a NUL byte (e.g. from untrusted/binary input or a malformed config value).","commonSituations":"Shell variable that failed to expand ($FILE unset becomes empty); template/interpolation bugs producing empty paths; programmatic callers splitting a string incorrectly; config files with 'path:' left blank.","solutions":["Pass a non-empty relative path, e.g. 'SKILL.md'","Fix the script/config that produced the empty value — echo the variable before calling to confirm it is populated","Strip stray whitespace and reject NUL characters in your own input handling before calling","If you meant the skill's main doc, explicitly pass 'SKILL.md'"],"exampleFix":"// before\nawait readOpenCliSkill('grok', process.env.SKILL_FILE ?? '');\n// after\nconst p = process.env.SKILL_FILE?.trim();\nif (!p) throw new Error('SKILL_FILE must be set');\nawait readOpenCliSkill('grok', p);","handlingStrategy":"validation","validationCode":"if (typeof p !== 'string' || !p.trim() || p.includes('\\0')) throw new Error('skill path must be a non-empty string without NUL');","typeGuard":"function isValidSkillPath(p: unknown): p is string {\n  return typeof p === 'string' && p.trim().length > 0 && !p.includes('\\0');\n}","tryCatchPattern":"try {\n  const skill = await readOpenCliSkill(name, p);\n} catch (e) {\n  if (e instanceof ArgumentError && /non-empty/i.test(e.message)) console.error('Skill path was empty — check the variable feeding it.');\n  else throw e;\n}","preventionTips":["Validate env vars/config values are non-empty before interpolation","Avoid `?? ''` fallbacks that silently produce empty paths","Trim and check inputs at the boundary of your script"],"tags":["argument-validation","empty-input","skills"],"backgroundTag":"empty-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}