jackwener/OpenCLI · error · ArgumentError

Skill path must be non-empty.

Error message

Skill path must be non-empty.

What it means

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).

Source

Thrown at src/skills.ts:104

function parseSkillTarget(target: string, relpath: string): { name: string; pathInSkill: string } {
  const normalizedTarget = normalizeSkillPath(target);
  if (relpath) {
    return { name: normalizedTarget, pathInSkill: relpath };
  }
  const slash = normalizedTarget.indexOf('/');
  if (slash === -1) {
    return { name: normalizedTarget, pathInSkill: '' };
  }
  return {
    name: normalizedTarget.slice(0, slash),
    pathInSkill: normalizedTarget.slice(slash + 1),
  };
}

function normalizeSkillPath(raw: string): string {
  const normalized = raw.trim().replace(/\\/g, '/');
  if (!normalized || normalized.includes('\0')) {
    throw new ArgumentError('Skill path must be non-empty.');
  }
  if (normalized.startsWith('/') || normalized.split('/').some((part) => part === '..')) {
    throw new ArgumentError(`Invalid skill path: ${raw}`, 'Use a path relative to an OpenCLI skill directory.');
  }
  return path.posix.normalize(normalized);
}

function parseFrontmatter(content: string): SkillFrontmatter {
  if (!content.startsWith('---\n')) return {};
  const end = content.indexOf('\n---', 4);
  if (end < 0) return {};
  try {
    const parsed = yaml.load(content.slice(4, end));
    return parsed && typeof parsed === 'object' ? parsed as SkillFrontmatter : {};
  } catch {
    return parseLooseFrontmatter(content.slice(4, end));
  }
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass a non-empty relative path, e.g. 'SKILL.md'
  2. Fix the script/config that produced the empty value — echo the variable before calling to confirm it is populated
  3. Strip stray whitespace and reject NUL characters in your own input handling before calling
  4. If you meant the skill's main doc, explicitly pass 'SKILL.md'

Example fix

// before
await readOpenCliSkill('grok', process.env.SKILL_FILE ?? '');
// after
const p = process.env.SKILL_FILE?.trim();
if (!p) throw new Error('SKILL_FILE must be set');
await readOpenCliSkill('grok', p);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof p !== 'string' || !p.trim() || p.includes('\0')) throw new Error('skill path must be a non-empty string without NUL');

Type guard

function isValidSkillPath(p: unknown): p is string {
  return typeof p === 'string' && p.trim().length > 0 && !p.includes('\0');
}

Try / catch

try {
  const skill = await readOpenCliSkill(name, p);
} catch (e) {
  if (e instanceof ArgumentError && /non-empty/i.test(e.message)) console.error('Skill path was empty — check the variable feeding it.');
  else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/fec6930adb318c9a. Report an issue: GitHub.