Bundling Scripts and Assets Inside a Claude Skill
A SKILL.md on its own can only instruct. The moment a workflow needs deterministic behaviour, a shell script that always formats dates the same way, a template that always has the same sections, a reference table too long to paste into instructions, you bundle files alongside the SKILL.md. This post covers what to bundle, how to lay the folder out, and the path mistakes that make bundled files silently useless.
New to skills entirely? Start with writing a Claude skill from scratch; this post builds on that foundation.
TL;DR: A skill is a folder, and everything in that folder ships with it. Put executable helpers in scripts/, fill-in templates in templates/ or assets/, and long reference material in separate Markdown files in references/ that SKILL.md points to, so the heavy content only loads when needed. Reference every bundled file from SKILL.md by its path relative to the skill folder, tell Claude explicitly when to run a script versus follow prose instructions, and prefer scripts for anything that must be exact: date maths, file conversion, validation, API payload shapes. Keep scripts dependency-light, mark them executable before zipping, and never bundle real credentials, since everything in the folder is plain text to anyone who installs it.
What belongs in a skill bundle?
| File type | Folder | When to use |
|---|---|---|
| Executable scripts (bash, Python, Node) | scripts/ |
Deterministic operations: parsing, conversion, validation, date maths |
| Templates (Markdown, HTML, JSON) | templates/ or assets/ |
Output that must always have the same structure |
| Long reference docs | references/ |
Material too long for SKILL.md, loaded only when a task needs it |
| Sample data or fixtures | examples/ |
Showing Claude the exact expected input and output |
| Fonts, SVG, images | assets/ |
Visual output like PDFs, diagrams, social cards |
The dividing rule: instructions describe judgement, scripts encode exactness. If two runs must produce byte-identical output, that logic belongs in a script, not in prose.
A complete example layout
invoice-generator/
SKILL.md
CHANGELOG.md
scripts/
validate_invoice.py
number_to_words.py
templates/
invoice.html
references/
tax-rules.md
examples/
sample-input.json
sample-output.html
And the SKILL.md that wires it together:
name: invoice-generator
description: Generates HTML invoices from structured job data. Use when the user asks to create, format, or validate an invoice.
# Invoice Generator
## Workflow
1. Collect job data from the user (client, line items, dates).
2. Validate it by running:
`python scripts/validate_invoice.py <input.json>`
Fix any reported errors before continuing.
3. Load `templates/invoice.html` and fill every `{{placeholder}}`.
4. For tax treatment questions, read `references/tax-rules.md`
rather than guessing.
5. Compare the result against `examples/sample-output.html`
for structure before presenting it.
Notice the pattern: SKILL.md stays short and points outward. The tax rules file might be 2,000 words, but it costs nothing until a task actually needs tax detail. This progressive loading is the main reason to split content out rather than inflating SKILL.md.
How should SKILL.md reference bundled files?
Always by path relative to the skill folder, and always with an explicit instruction about what to do with the file: run it, read it, or copy it. Three verbs, three different behaviours.
Bad, because it is ambiguous:
See validate_invoice.py for validation.
Good, because it is an instruction:
Run `python scripts/validate_invoice.py <input.json>` and stop if it
reports errors. Do not attempt to validate the JSON manually.
That last sentence matters. Without it, Claude may reasonably decide to eyeball the JSON instead of running your script, and you lose the exactness that justified writing the script in the first place.
Writing scripts that survive other machines
Your skill will run in environments you did not set up. Defensive habits:
- Minimise dependencies. Standard library only if you can. A script that needs
pip installof three packages will fail on clean environments; if a dependency is unavoidable, have the script check for it and print an install hint rather than a stack trace. - Fail loudly and legibly. Exit non-zero with a one-line human-readable reason. Claude reads stderr; make it useful.
- Take input as arguments or stdin, not hardcoded paths. Never write
/Users/yourname/...inside a bundled script. - Mark scripts executable (
chmod +x scripts/*.sh) before zipping, and keep a shebang line on every script. - No secrets, ever. Use placeholders such as
YOUR_API_KEYand read real values from environment variables at runtime. Everything in the folder is readable plain text once shared, as covered in publishing and sharing Claude skills.
#!/usr/bin/env bash
# scripts/fetch_report.sh
if [ -z "$REPORT_API_KEY" ]; then
echo "ERROR: set REPORT_API_KEY in your environment first" >&2
exit 1
fi
curl -s -H "Authorization: Bearer $REPORT_API_KEY" "$1"
Templates: lock the structure, free the content
Templates earn their place when output structure is non-negotiable. Use unmistakable placeholders and list them in SKILL.md so nothing gets missed:
<div class="invoice-header">
<h1>Invoice {{INVOICE_NUMBER}}</h1>
<p>Issued: {{ISSUE_DATE}} | Due: {{DUE_DATE}}</p>
</div>
Fill every placeholder: INVOICE_NUMBER, ISSUE_DATE, DUE_DATE,
CLIENT_NAME, LINE_ITEMS, TOTAL. If a value is unknown, ask the
user; never leave a raw {{placeholder}} in output.
Common bundling mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Absolute paths in scripts or SKILL.md | Works for you, breaks for everyone | Paths relative to the skill folder only |
| Everything inlined in SKILL.md | Slow, bloated context on every trigger | Split long material into references/ |
| No instruction on when to run scripts | Claude improvises instead of running them | Explicit run commands with "do not do this manually" |
| Bundled virtualenv or node_modules | Enormous zip, platform-specific binaries | Bundle source only; document dependencies |
| Real API key in an example | Credential leak on first share | Placeholders plus environment variables |
Testing the bundle
Bundled files add failure modes that plain instructions do not have, so test beyond triggering: in a cold session, confirm the scripts actually get executed (not paraphrased), the templates get filled completely, and the references get read when relevant. The skill testing and eval guide covers building this into a repeatable eval set, and if this skill hands output to another skill, verify the handoff format too per skill chaining.
Frequently asked questions
Is there a size limit on bundled files?
Practical limits matter more than hard ones: huge bundles are slow to share and clone, and giant reference files defeat the point of progressive loading. Keep individual references focused and split them by topic.
Can a script in one skill call a script in another skill?
Avoid it. Skills install independently, so cross-skill paths break whenever one of the pair is missing. If two skills need the same helper, duplicate it in both or promote both skills into a single plugin that shares the file.
Should bundled scripts be Python or bash?
Whichever is most likely present and least likely to need dependencies. Bash for file plumbing, Python for anything with logic, parsing, or maths. Declare the requirement in SKILL.md either way.
About the author
Yanni Papoutsis builds AI products, automation pipelines, and technical documentation with Claude, and publishes free tooling and guides at yanni.uk.
Next step: Make sure your bundled skill fires reliably with how skill triggers work, or explore 1,000+ AI tools at yanni.uk/ai-tools.
Sources
- Claude documentation (agent skills): https://docs.claude.com
- Anthropic: https://www.anthropic.com