Skip to content

How to Write Effective SKILL.md Files for AI Coding Assistants

Problem

My SKILL.md file wasn’t triggering. I’d create a skill with what I thought was a clear description, but Claude would never invoke it automatically. I’d have to manually request it, which defeated the purpose of having skills at all.

Here’s what my first attempt looked like:

SKILL.md (BROKEN)
---
name: kotlin
description: Use this skill for Kotlin development
---
## Kotlin Guidelines
- Use idiomatic Kotlin
- Prefer immutability
- Write tests

When I worked with Kotlin files, nothing happened. Claude didn’t recognize the skill. I had to explicitly ask for it every time.

I tried making the description more detailed:

SKILL.md (STILL BROKEN)
---
name: kotlin
description: |
This skill helps with Kotlin development tasks.
Includes coding conventions, testing patterns, and best practices.
Good for Spring Boot, Kotlin/JS, and Android projects.
---

Still nothing. The skill sat unused in my skills folder.

What Happened?

I dug into how Claude Code processes skills and found the issue: my descriptions were too vague.

Claude transforms the description field into a tool definition. It uses this description to decide when to invoke the skill automatically. When the description says “Use this skill for Kotlin development,” Claude can’t determine if the current task matches that criteria.

The description needs to answer: What specific actions or file types should trigger this skill?

Looking at working examples, I noticed a pattern:

VAGUE: "Use for Kotlin stuff"
SPECIFIC: "Use when reading, writing, editing *.kt or *.kts files"

The specific version gives Claude clear conditions to match against.

How I Fixed It

I rewrote my Kotlin skill with explicit trigger conditions:

SKILL.md (WORKING)
---
name: kotlin
description: |
Use this skill when working with Kotlin code in any capacity:
- Reading, writing, editing, or reviewing Kotlin files (*.kt, *.kts)
- Running Gradle tasks for Kotlin modules
- Writing or debugging Kotlin/JS code that targets Node.js
- Working with external JavaScript libraries from Kotlin
- Writing tests for Kotlin code (@Test, @BeforeTest, @AfterTest)
- Setting up dependency injection or mocking in Kotlin
- Dealing with multiplatform Kotlin projects
- Troubleshooting Kotlin compilation or runtime errors
---
## Key Reminders
- Prefer `val` over `var` - immutability first
- Use immutable collection interfaces (`List`, `Set`, `Map`)
- Use `it` for short lambdas, named parameters for complex ones
- Prefer expression form of `if`, `when`, `try`
## Code Patterns
### Good: Immutable Data Class
```kotlin title="User.kt"
data class User(
val id: String,
val name: String,
val email: String
)

Bad: Mutable Properties

User.kt
data class User(
var id: String, // MUTABLE!
var name: String, // MUTABLE!
var email: String // MUTABLE!
)

External Resources

The full Kotlin coding conventions are cached locally at kotlin-coding-conventions.md.

After this change, the skill started triggering automatically. When I opened a `.kt` file, Claude invoked the skill without me asking.
## The Difference That Made It Work
I tested both approaches systematically.
**Test 1: Opening a Kotlin file**
With vague description:

User: opens User.kt Claude: [no skill invocation, proceeds normally]

With specific description:

User: opens User.kt Claude: [Invoking kotlin skill - Kotlin file detected] Following Kotlin conventions for this session…

**Test 2: Running a Gradle task**
With vague description:

User: ./gradlew build Claude: [no skill invocation]

With specific description:

User: ./gradlew build Claude: [Invoking kotlin skill - Gradle task detected] Applying Kotlin build conventions…

**Test 3: Debugging a Kotlin error**
With vague description:

User: Getting “Unresolved reference” in my Kotlin code Claude: [generic debugging, no skill invocation]

With specific description:

User: Getting “Unresolved reference” in my Kotlin code Claude: [Invoking kotlin skill - Kotlin error detected] Checking import statements and module dependencies…

The trigger conditions in the description made all the difference.
## Why This Works
Claude processes the description field as a classification prompt. It asks: "Does the current context match this description?"
Specific descriptions create clear matching criteria:
| Vague Description | Specific Description |
|------------------|----------------------|
| "Use for Kotlin" | "Use when reading/writing/editing *.kt files" |
| "Helps with testing" | "Use when writing @Test, @BeforeTest, @AfterTest" |
| "For Gradle" | "Use when running Gradle tasks for Kotlin modules" |
The specific versions have binary answers: yes or no. Either I'm editing a `.kt` file or I'm not. Either I'm running a Gradle task or I'm not.
Vague descriptions create ambiguity. "Kotlin development" could mean many things. Claude errs on the side of not invoking, which means you get no automatic help.
## Common Mistakes I Made
### Mistake 1: Describing What the Skill Does Instead of When to Use It
```yaml title="WRONG"
description: |
This skill provides Kotlin coding conventions,
best practices, and testing patterns.

This tells Claude what’s inside the skill, not when to invoke it. Claude can’t determine if the current task needs “Kotlin coding conventions.”

RIGHT
description: |
Use this skill when working with Kotlin files (*.kt, *.kts)
or running Kotlin-related Gradle tasks.

This gives Claude a condition to check: is the user working with Kotlin files?

Mistake 2: Overly Broad Descriptions

WRONG
description: Use for all backend development

This is too broad. Backend development could mean Java, Python, Go, Node.js, etc. Claude won’t know when this specific skill applies.

RIGHT
description: |
Use for Spring Boot applications in Kotlin:
- Creating REST controllers
- Configuring Spring Security
- Writing JPA entities
- Setting up dependency injection

This narrows the scope to specific, recognizable patterns.

Mistake 3: Missing File Patterns

I initially forgot to include file patterns in my descriptions. Without them, Claude had no easy way to trigger.

WRONG
description: Use for Python web development
RIGHT
description: |
Use when working with:
- Flask applications (*.py files with Flask imports)
- FastAPI projects (main.py, routers/*.py)
- Python web tests (test_*.py, *_test.py)

The file patterns give Claude something concrete to match against.

Mistake 4: Not Caching External Docs

Skills that reference external documentation should cache it locally. Network lookups during skill invocation are slow and unreliable.

WRONG
## External Resources
See https://kotlinlang.org/docs/coding-conventions.html for full guidelines.
RIGHT
## External Resources
The full Kotlin coding conventions are cached locally at `kotlin-coding-conventions.md`.

A Template That Works

After fixing several skills, I developed a template that consistently works:

SKILL.md Template
---
name: [skill-name]
description: |
Use this skill when:
- [Specific file type or pattern]
- [Specific command or action]
- [Specific error or issue]
- [Specific framework or library usage]
---
## Quick Reference
- [Key rule 1]
- [Key rule 2]
- [Key rule 3]
## Code Patterns
### Pattern Name
```text title="[filename]"
[good example code]

Avoid This

[filename]
[bad example code]

External Resources

[Link or reference to cached documentation]

The key elements:
1. **Name**: Short, lowercase, hyphenated identifier
2. **Description**: Bullet list of specific trigger conditions
3. **Quick Reference**: 3-5 key rules for immediate reference
4. **Code Patterns**: Good and bad examples
5. **External Resources**: Links or cached documentation
## How I Test Skills Now
After writing a new skill, I test it systematically:
**Step 1: File trigger test**

Action: Create/open a matching file Expected: Skill invokes automatically

**Step 2: Command trigger test**

Action: Run a matching command Expected: Skill invokes automatically

**Step 3: Error trigger test**

Action: Encounter a matching error Expected: Skill invokes automatically

**Step 4: Negative test**

Action: Work with unrelated files/commands Expected: Skill does NOT invoke

If any test fails, I adjust the description until the behavior matches expectations.
## Summary
In this post, I showed how to write SKILL.md files that actually trigger automatically. The key insight is that descriptions need specific, matchable conditions - not vague explanations of what the skill does.
The difference between working and broken skills came down to one change: replacing "Use for Kotlin development" with "Use when reading, writing, editing *.kt files, running Gradle tasks for Kotlin modules, debugging Kotlin errors, etc."
This transforms the description from a personality trait into a classification prompt. Claude can now determine with certainty whether the current context matches the skill's purpose.
**Checklist for effective SKILL.md files:**
- [ ] Description lists specific file patterns (*.kt, *.kts)
- [ ] Description includes specific commands (gradlew build)
- [ ] Description mentions specific errors (Unresolved reference)
- [ ] Content has quick reference section (3-5 rules)
- [ ] Content shows good/bad code examples
- [ ] External documentation is cached locally
- [ ] Skill passes trigger tests for matching context
- [ ] Skill passes negative tests for non-matching context
<FinalWords reflinks={frontmatter.reflinks} />

Comments