# Claude Code Skills ## Skill Structure - Each skill lives in `skills//SKILL.md` - Skills should be project-agnostic where possible — use dynamic context injection to adapt - After adding a new skill, run the install script to register it - Skills only useful for one project should live in that project's `.claude/skills/` instead ## Authoring Guidelines - **Inline by default** — only use `context: fork` if the skill genuinely doesn't need conversation history - **Pre-fetch context** with `!`command`` injection to reduce tool calls during execution - **Restrict tools** with `allowed-tools` to the minimum needed — reduces permission prompts - **Use $ARGUMENTS** for user input, `$0`, `$1` etc. for positional args - Dynamic commands in `!`command`` run at skill load time, not during Claude's execution ## Portable Path Resolution - **Use `CLAUDE_PROJECT_ROOT` env var** for cross-project path references in `!`command`` blocks. Hardcoded absolute paths (e.g., `~/dev/claude/...`) are user-specific. Relative paths (`../`) break depending on CWD and can trigger sandbox violations when they resolve outside allowed directories. - **Add a detection fallback.** Include a "Step 0" in skill instructions that detects the project root by walking up the directory tree to find the highest `CLAUDE.md` if the env var isn't set. This makes skills work even without prior setup. - **Keep config paths relative to the root.** Settings files should use paths relative to `CLAUDE_PROJECT_ROOT` (e.g., `projects_dir: projects`) rather than absolute paths, so they're portable across machines. ## Skill Discovery Timing - **Skills are discovered at session start, not dynamically.** Creating or symlink a new skill mid-session requires restarting Claude Code to use it as a slash command. - **Broken symlinks cause silent failures under `set -e`.** `readlink -f` on a broken symlink returns empty string. The install script should validate symlinks and remove stale ones. ## `!`command`` Gotchas - **No `$()` command substitution** — the permission checker rejects commands containing `$()` - **No complex shell pipelines relying on subshells** — keep commands simple and self-contained - **`allowed-tools` patterns must match the command binary** — each binary used in `!`command`` blocks needs its own pattern - **Prefer specific tool patterns over broad ones** — `Bash(git log *)` is safer than `Bash(git *)` - **Fallback to tool instructions for dynamic paths** — if a command needs `$ARGUMENTS` to compute a path, use a plain-text instruction telling Claude to use the Read tool instead - **Env var expansion works** — `${CLAUDE_PROJECT_ROOT}` expands in `!`command`` blocks because they run as shell commands. This is the recommended pattern for portable cross-project paths. ## Non-ASCII in YAML Frontmatter Skills with em dashes (`—`), smart quotes (`"`), or other non-ASCII characters in the YAML frontmatter `description` field fail to load silently — the skill appears as "Unknown skill" with no error message. The markdown body below the frontmatter can contain any characters. AI models commonly generate em dashes instead of regular dashes. Always validate skill files (e.g., with `cat -A` or a dedicated validator) before committing. ## Profile-Independent Skills Directories Each Claude Code profile maintains a completely independent skills directory. Skills installed in one profile (e.g., default) are unavailable in other profiles (e.g., `.claude-octopus`). Install scripts must use the profile-aware config directory path rather than hardcoded paths like `~/.claude/skills/`. ## Research Failure History Before Building Validators When building a tool that detects known problems (like a linter rule or a validator), research all historical failures first — session logs, git commit history, issue trackers. Documentation alone misses non-obvious failure patterns. The upfront research investment produces comprehensive coverage that incremental discovery cannot match. ## Task Decomposition for Independent Agents When breaking work into tasks for independent agents (container-based or otherwise): - **Task prompts must be fully self-contained** — agents have no conversation history from the decomposer - **Include explicit "read these files first" instructions** in each task prompt - **Balance granularity** — over-decomposing creates merge overhead; under-decomposing wastes parallelism potential - **Scope each task to one deliverable** with clear reads (inputs) and writes (outputs) to minimise conflicts - **Use 3+ parallel research agents before architecture decisions.** Survey competing tools, best practices, and user patterns in parallel before committing to a design. Breadth of input prevents tunnel vision during planning. ## Make Review Skills Read-Only Skills that review artifacts (plans, specs, designs) should be read-only — restrict allowed-tools to Read, Glob, Grep, and safe Bash commands. Review output informs the human rather than auto-editing, which avoids unintended changes and reduces permission prompts. Load best-practice context upfront — better to load too much reference material than to miss a relevant check. ## Separate Formatter Exit Codes from Hook Exit Codes When integrating formatters with Claude Code hooks, keep formatter scripts and hook dispatch logic separate. Formatter scripts exit 0 (clean) or 1 (lint errors). The dispatch hook decides the final exit code semantically (e.g., exit 2 for PostToolUse feedback). This separation means the same formatter scripts work for both PostToolUse hooks and pre-commit hooks without modification. ## Pre-fetching Bounded Metadata vs. Unbounded Content When a skill needs to examine large external artifacts (transcripts, logs, dumps), have a helper script pre-gather **metadata only** (a small JSON list: paths, sizes, headers) at skill-load time via `!`command``. Delegate the actual content extraction to a subagent invoked from the skill's instructions. This keeps the main conversation's context small — the skill sees a compact index rather than megabytes of raw content — and lets each step pick the cheapest/strongest model for the job (metadata triage with Haiku, deep extraction with Sonnet). Never pre-fetch unbounded content via `!`command``; it bloats the system prompt and often blows past context limits. ## Shell Wrappers to Work Around `!`command`` Restrictions When a skill needs dynamic values like `$(pwd)` in its pre-fetch command, write a thin shell wrapper script that does the substitution internally and expose the wrapper in `!`command``. The Claude Code permission checker rejects `$()` inside bang commands (see `!`command`` Gotchas above), but a wrapper invoked as a plain binary is fine. Document the wrapper's reason-for-existence in a comment at the top of the script so it can be removed if the `$()` restriction ever lifts. Keep wrappers minimal — one responsibility each — so the indirection doesn't obscure what the skill is actually doing. ## Model Selection for Subagents by Task Type When a skill spawns a subagent for a subtask, pick the model by cognitive demand: | Task type | Model | |---|---| | Deterministic extraction, reformatting, metadata triage | Haiku | | Judgment required — detecting backtracking, gotchas, intent | Sonnet | | Planning, architecture, cross-file synthesis | Opus | Document the rationale in the skill (a comment near the subagent invocation is enough) so future edits don't silently downgrade quality by picking a cheaper model without revisiting whether the task actually fits it. ## Subagent Path Discipline Subagents inherit no cwd context from the parent skill — they start in whatever working directory the harness gives them, which is rarely where the parent was invoked. When a subagent writes output files, **pass absolute paths in its prompt** and never rely on relative paths resolving the way the parent expects. Verify on the first real run that files land where intended; a subagent writing to a surprise cwd often fails silently (the parent looks for the file, doesn't find it, falls through to a default). `CLAUDE_PROJECT_ROOT` (see Portable Path Resolution above) is the canonical anchor — compute absolute output paths from it before handing them to the subagent. ## Separate Skill-Helper Scripts from General Scripts Not every script in a repo is a skill helper. Maintain an explicit allowlist (e.g., a `SKILL_HELPERS` array in the installer) of scripts that get symlinked into the profile's skills-reachable dir (`$CLAUDE_CONFIG_DIR/scripts/`). Other scripts stay callable only by absolute repo path. **Key points:** - **Symlinks, not copies.** Edits to the repo go live immediately without reinstalling. - **Hooks follow the same installer pattern** via their own allowlist loop — don't conflate hooks with general skill helpers. - **Explicit allowlist beats glob.** Auto-symlinking every script means new unrelated scripts silently become skill-reachable, which is a permission-scope surprise.