How Scripts Work in Claude Skills

Research notes — compiled from the official Claude Code docs and the Agent Skills open standard

Contents
  1. Overview: What Is a Skill?
  2. Skill Directory Structure
  3. How Scripts Under scripts/ Are Used by Claude Code
  4. Conventions for Writing Skill Scripts
  5. Referencing Scripts from SKILL.md
  6. Self-Contained Scripts (Inline Dependencies)
  7. Dynamic Context Injection with ! Commands
  8. Full Example: Codebase Visualizer
  9. Quick Checklist

1. Overview: What Is a Skill?

A skill is a folder containing a SKILL.md file (with YAML frontmatter + Markdown instructions) plus optional supporting files. Claude Code discovers skills in:

LocationPathScope
Personal~/.claude/skills/<name>/SKILL.mdAll your projects
Project.claude/skills/<name>/SKILL.mdThis project only
Plugin<plugin>/skills/<name>/SKILL.mdWhere plugin enabled
EnterpriseManaged settingsEntire org

Skills follow the Agent Skills open standard (adopted by Claude Code, Cursor, VS Code, Gemini CLI, Roo Code, and many others). Claude Code extends the standard with invocation control, subagent execution, and dynamic context injection.

2. Skill Directory Structure

my-skill/
├── SKILL.md           # Required — metadata + instructions (the entrypoint)
├── scripts/           # Optional — executable code Claude can run
│   ├── validate.sh
│   └── process.py
├── references/        # Optional — extra docs loaded on demand
│   └── REFERENCE.md
├── assets/            # Optional — templates, schemas, data files
│   └── template.json
└── examples/          # Optional — example outputs
    └── sample.md

Key points from the spec:

3. How Scripts Under scripts/ Are Used by Claude Code

Key Insight

Scripts are not automatically executed when a skill loads. They are files that Claude is instructed to run via the Bash tool, according to the instructions in SKILL.md. The scripts/ directory is simply a conventional place to put executable code that the skill's instructions tell Claude to run.

Here's the full lifecycle:

  1. Discovery — Claude Code scans skill directories and loads each skill's name and description into context so Claude knows what's available (~100 tokens per skill).
  2. Activation — When a skill is invoked (by the user via /skill-name, or by Claude when it decides the skill is relevant), the full SKILL.md body is loaded into Claude's context.
  3. Instruction following — Claude reads the SKILL.md instructions, which tell it to run specific scripts. Claude uses its Bash tool to execute them.
  4. Script execution — Claude runs the script as a shell command. The script's stdout/stderr are returned to Claude, which then uses the output to continue its task.

Scripts are not magic. They are regular files (Python, Bash, JS, etc.) that Claude runs via its Bash tool. The scripts/ directory is purely a convention — you reference scripts from SKILL.md using relative paths, and Claude executes them as shell commands.

How Claude resolves script paths

There are two approaches:

A. Relative paths from SKILL.md

You reference scripts relative to the skill directory root. Claude resolves these automatically:

# In SKILL.md:
Run the validation script:
```bash
bash scripts/validate.sh "$INPUT_FILE"
```

B. Using ${CLAUDE_SKILL_DIR}

For scripts that need absolute paths (e.g., when the working directory might be different), use the ${CLAUDE_SKILL_DIR} substitution variable, which resolves to the directory containing the skill's SKILL.md:

# In SKILL.md:
```bash
python ${CLAUDE_SKILL_DIR}/scripts/visualize.py .
```

Or with a hardcoded personal skill path:

python ~/.claude/skills/codebase-visualizer/scripts/visualize.py .

The allowed-tools connection

If your script needs Claude to run Bash commands, you can pre-approve them in frontmatter so Claude doesn't ask for permission each time:

---
name: my-skill
allowed-tools: Bash(python *), Bash(bash *)
---

4. Conventions for Writing Skill Scripts

These conventions come from the Agent Skills "Using Scripts" guide and are critical for making scripts work well with Claude (or any AI agent):

🚫 No interactive prompts

This is a hard requirement. Agents run scripts in non-interactive shells — they cannot respond to TTY prompts, password dialogs, or confirmation menus. A script that blocks on input will hang indefinitely.

# ❌ Bad: hangs waiting for input
$ python scripts/deploy.py
Target environment: _

# ✅ Good: accepts input via flags
$ python scripts/deploy.py --env staging --tag v1.2.3

# ✅ Good: clear error when required args missing
$ python scripts/deploy.py
Error: --env is required. Options: development, staging, production.
Usage: python scripts/deploy.py --env staging --tag v1.2.3

📖 Implement --help

--help output is the primary way an agent learns your script's interface. Include a brief description, available flags, and usage examples:

Usage: scripts/process.py [OPTIONS] INPUT_FILE

Process input data and produce a summary report.

Options:
  --format FORMAT   Output format: json, csv, table (default: json)
  --output FILE     Write output to FILE instead of stdout
  --verbose         Print progress to stderr

Examples:
  scripts/process.py data.csv
  scripts/process.py --format csv --output report.csv data.csv

📝 Write helpful error messages

When the agent gets an error, the message directly shapes its next attempt. Say what went wrong, what was expected, and what to try:

# ❌ Opaque
Error: invalid input

# ✅ Actionable
Error: --format must be one of: json, csv, table. Received: "xml"

📊 Use structured output

Prefer JSON, CSV, or TSV over free-form text. Send structured data to stdout and diagnostics to stderr:

# ❌ Hard to parse
NAME        STATUS    CREATED
my-service  running   2025-01-15

# ✅ Unambiguous
{"name": "my-service", "status": "running", "created": "2025-01-15"}

🔄 Further design considerations

PrincipleWhy
IdempotencyAgents may retry commands. "Create if not exists" is safer than "create and fail on duplicate."
Input constraintsReject ambiguous input with a clear error. Use enums and closed sets where possible.
Dry-run supportFor destructive operations, a --dry-run flag lets the agent preview what will happen.
Meaningful exit codesUse distinct exit codes for different failure types and document them in --help.
Safe defaultsDestructive operations should require explicit --confirm or --force flags.
Predictable output sizeAgent harnesses truncate output beyond ~10-30K chars. Default to summaries; support --offset or --output FILE for large data.

5. Referencing Scripts from SKILL.md

List available scripts and tell the agent how to run them:

## Available scripts
- **`scripts/validate.sh`** — Validates configuration files
- **`scripts/process.py`** — Processes input data

## Workflow
1. Run the validation script:
   ```bash
   bash scripts/validate.sh "$INPUT_FILE"
   ```
2. Process the results:
   ```bash
   python3 scripts/process.py --input results.json
   ```

Script execution paths (in code blocks) are relative to the skill directory root, because the agent runs commands from there. The same relative-path convention works in support files like references/*.md.

6. Self-Contained Scripts (Inline Dependencies)

When you need third-party packages, use inline dependency declarations so the script is self-contained — no separate requirements.txt or install step needed:

Python (PEP 723 + uv)

#!/usr/bin/env python3
# /// script
# dependencies = [
#     "beautifulsoup4>=4.12,<5",
#     "requests>=2.31",
# ]
# requires-python = ">=3.10"
# ///

from bs4 import BeautifulSoup
import requests
# ... your code ...

Run with: uv run scripts/extract.py — uv creates an isolated environment, installs deps, and runs it.

Deno (TypeScript/JavaScript)

import { parse } from "jsr:@std/csv@1.0.4";
// ... Deno auto-resolves jsr: and npm: imports

Run with: deno run scripts/process.ts

Bun

import { $ } from "bun";
// bun auto-installs missing packages

Tip: Pin versions in your inline dependencies (e.g. "beautifulsoup4>=4.12,<5") so the script behaves the same over time.

7. Dynamic Context Injection with ! Commands

Claude Code extends the Agent Skills standard with a preprocessing feature: the !`command` syntax runs shell commands before the skill content is sent to Claude. Output replaces the placeholder:

---
name: pr-summary
description: Summarize changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh *)
---

## Pull request context
- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task
Summarize this pull request...

This is preprocessing, not something Claude executes. Claude only sees the final rendered output. This is different from scripts in scripts/ which Claude actively runs during its task.

8. Full Example: Codebase Visualizer Skill

This example from the official docs shows the complete pattern — a skill that bundles a Python script to generate an interactive HTML visualization:

Directory layout

~/.claude/skills/codebase-visualizer/
├── SKILL.md
└── scripts/
    └── visualize.py

SKILL.md

---
name: codebase-visualizer
description: Generate an interactive collapsible tree visualization of your
  codebase. Use when exploring a new repo, understanding project structure,
  or identifying large files.
allowed-tools: Bash(python *)
---

# Codebase Visualizer

Generate an interactive HTML tree view of your project's file structure.

## Usage

Run the visualization script from your project root:

```bash
python ~/.claude/skills/codebase-visualizer/scripts/visualize.py .
```

This creates `codebase-map.html` in the current directory and opens it
in your default browser.

## What the visualization shows

- **Collapsible directories**: Click folders to expand/collapse
- **File sizes**: Displayed next to each file
- **Colors**: Different colors for different file types
- **Directory totals**: Shows aggregate size of each folder

scripts/visualize.py

A regular Python script using only stdlib (json, sys, webbrowser, pathlib, collections). It scans a directory tree and generates a self-contained HTML file. Key properties:

9. Quick Checklist for Writing Skill Scripts

#ConventionDetails
1Place scripts in scripts/Standard directory name per the Agent Skills spec
2Document scripts in SKILL.mdList each script with a description and exact run command
3No interactive promptsAccept all input via CLI flags, env vars, or stdin
4Implement --helpAgent's primary way to learn the interface
5Helpful error messagesSay what went wrong, what was expected, and what to try
6Structured output (JSON/CSV)stdout for data, stderr for diagnostics
7Be idempotentAgents may retry; "create if not exists" is safer
8Pin dependency versionsUse PEP 723 inline deps with version ranges
9Self-contained if possibleUse uv run/deno run/npx for auto-dependency resolution
10Meaningful exit codesDifferent codes for different failure types
11Safe defaultsDestructive ops need --confirm or --force
12Predictable output sizeDefault to summaries; support --offset for pagination
13Set allowed-toolsPre-approve Bash(python *) etc. so Claude doesn't ask each time
14Use ${CLAUDE_SKILL_DIR}For absolute paths that work regardless of cwd

Sources