Files
small-scripts/specs/validate-skill.spec.md
Paul O'Reilly a9d54dcf7f Add validate-skill: SKILL.md linter for Claude Code skills
Checks for common issues that silently break skills — non-ASCII
frontmatter, $VAR in paths, uncovered binaries in allowed-tools, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:14:20 +13:00

4.2 KiB

validate-skill

Purpose

Validate Claude Code SKILL.md files against known restrictions that cause permission checker failures, sandbox errors, and other breakages.

Usage

validate-skill [OPTIONS] <PATH>

Arguments

Argument Default Description
PATH . (current directory) A SKILL.md file or a directory to scan recursively

Flags

Flag Short Description
--dryrun -n List SKILL.md files that would be checked without validating
--help -h Show usage information

Behaviour

  1. Discovery phase: If PATH is a file, validate that one file. If PATH is a directory, find all files named SKILL.md recursively.

  2. Validation phase: For each SKILL.md file, run these checks:

    Frontmatter checks: a. File starts with --- on line 1 b. A closing --- delimiter exists on a subsequent line c. name: field exists, contains only lowercase letters, numbers, and hyphens, max 64 characters d. description: field exists and is non-empty (multi-line > syntax counts as non-empty)

    e. Non-ASCII characters in frontmatter (em dashes, smart quotes, curly apostrophes, etc.) — these can silently break YAML parsing and prevent the skill from loading

    Bang-command checks (lines matching ! followed by a backtick): e. No ${VAR} syntax — must use $VAR instead f. No $() command substitution g. No ~/ paths (sandbox-fragile) h. No ../ relative paths (CWD-fragile) i. Warning (not error) for hardcoded /home/ paths

    allowed-tools coverage checks: j. For each bang-command, the command binary (first word) must match a Bash(binary *) or Bash(binary) pattern in allowed-tools k. Warning for overly broad patterns like Bash(git *) l. Warning if bang-commands exist but no allowed-tools is declared

  3. Report phase: For each file:

    • Print the file path
    • Print each issue with severity (ERROR/WARN), line number, and description
    • Print per-file summary: N errors, M warnings
    • At the end, print total summary across all files
  4. Exit code:

    • 0 — no errors (warnings are OK)
    • 1 — at least one error found
    • 2 — usage error (bad arguments)

Dryrun Behaviour

When --dryrun is passed:

  • Perform the discovery phase only
  • Print each SKILL.md path, one per line
  • Prefix output with [dryrun] Would validate N files:
  • Exit code is always 0

Edge Cases

Scenario Handling
No SKILL.md files found Print "No SKILL.md files found in " and exit 0
File is not named SKILL.md (when given directly) Validate it anyway (user explicitly chose it)
No frontmatter at all Report missing --- delimiter error, skip field checks
No bang-commands and no allowed-tools Valid (like the linter skill)
Bang-commands exist but no allowed-tools declared Warning: commands won't be pre-authorised
$HOME (without braces) in bang-command No error — $HOME works fine, only ${HOME} is rejected
Multi-line description: > Treat as non-empty
Commands with || echo "fallback" Binary is the first word only
Bang-commands inside markdown code blocks Still checked (they execute regardless of markdown context)

Examples

Clean file

── skills/log/SKILL.md ──
   No issues found.

Summary: 1 file checked, 0 errors, 0 warnings

File with issues

── skills/distill/SKILL.md ──
   ERROR  Line 18: Bang-command uses ${VAR} syntax (use $VAR instead)
   ERROR  Line 22: Bang-command uses $() command substitution
   WARN   Line 8:  'Bash(git *)' is overly broad — use specific subcommands
   2 errors, 1 warning

Summary: 1 file checked, 2 errors, 1 warning

Directory scan

── skills/log/SKILL.md ──
   No issues found.

── skills/reflect/SKILL.md ──
   No issues found.

── skills/distill/SKILL.md ──
   ERROR  Line 18: Bang-command uses ${VAR} syntax (use $VAR instead)
   1 error, 0 warnings

Summary: 3 files checked, 1 error, 0 warnings

Dryrun

[dryrun] Would validate 3 files:
  skills/log/SKILL.md
  skills/reflect/SKILL.md
  skills/distill/SKILL.md