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>
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
-
Discovery phase: If
PATHis a file, validate that one file. IfPATHis a directory, find all files namedSKILL.mdrecursively. -
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$VARinstead f. No$()command substitution g. No~/paths (sandbox-fragile) h. No../relative paths (CWD-fragile) i. Warning (not error) for hardcoded/home/pathsallowed-tools coverage checks: j. For each bang-command, the command binary (first word) must match a
Bash(binary *)orBash(binary)pattern inallowed-toolsk. Warning for overly broad patterns likeBash(git *)l. Warning if bang-commands exist but noallowed-toolsis declared -
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
-
Exit code:
0— no errors (warnings are OK)1— at least one error found2— 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