Skills 101: Building Your First Claude Code Skill
I wanted a consistent way to turn meeting transcripts into Slack-ready notes. Not "summarize this call" typed fresh every time, but a repeatable workflow: parse the transcript, structure it by meeting type, format it for Slack. Same quality every time, no re-prompting.
Claude Code skills turned out to be the right tool. A skill is a markdown file that encodes a reusable workflow. You write it once, and Claude follows it every time the task comes up. No code, no API, no build step.
This post walks through building one from scratch in about 80 lines of markdown.
Contents
- What is a skill?
- What we're building
- Step 1: The frontmatter
- Step 2: The workflow skeleton
- Step 3: The Parse step
- Step 4: The Structure step
- Step 5: The Deliver step
- The complete skill
- Setting it up
- What makes a good skill
- Evolving the skill
What is a skill?
A skill is a markdown file that tells Claude how to do something. Not a one-shot prompt, but a reusable workflow that Claude follows every time it's triggered. It lives in your project directory, and Claude reads it when it recognizes the trigger.
Think of it as the difference between telling a new hire "write up the meeting notes" (vague, inconsistent results every time) and giving them a template with clear instructions for what to capture, how to format it, and where to post it (consistent, high-quality output).
A skill has three parts:
- Frontmatter: name, description, and trigger keywords (YAML)
- Workflow: the steps Claude follows, in order (markdown)
- Output spec: what the final result looks like (examples/templates)
That's it. You write a markdown file, put it in the right place, and Claude uses it.
What we're building
A meeting notes skill with this pipeline:
Granola transcript → Meeting Notes Skill → Slack-ready notes
The user pastes a Granola transcript (or drops the file). Claude parses it, structures the notes by meeting type, and formats them for Slack. Three steps, one skill file.
Step 1: The frontmatter
Every skill starts with YAML frontmatter. This is how Claude knows the skill exists and when to use it.
---
name: meeting-notes
description: >
Parse meeting transcripts (Granola, Otter, or raw notes) into structured
notes with decisions, action items, and key discussion points.
Format for Slack delivery. Triggers on: meeting notes, transcript,
standup notes, meeting summary, action items from meeting.
---
Two fields matter:
name: the skill identifier. Keep it short, kebab-case.description: the trigger mechanism. Claude reads this to decide whether the skill applies to what the user is asking. Be specific about inputs ("meeting transcripts, Granola"), outputs ("structured notes"), and use cases ("standup notes, meeting summary"). TheTriggers on:line is a convention for listing the phrases that should activate this skill.
The description isn't just documentation. It's functional. If your description says "Parse meeting transcripts" but the user says "summarize this call," Claude might not trigger the skill. Include the vocabulary your users actually use.
Step 2: The workflow skeleton
Below the frontmatter, define the workflow. Start with an overview so Claude (and future you) can see the full shape:
# Meeting Notes
Parse meeting transcripts into structured, Slack-ready notes.
## Workflow
1. **Parse**: Read the transcript, identify meeting type, extract raw content
2. **Structure**: Organize by decisions, action items, discussion points
3. **Deliver**: Format for Slack and present to user
Three steps. That's the right size for a first skill. The portfolio analysis skill I built has six steps and runs to 942 lines, but it took two iterations and a real-world stress test to get there. Start small.
Step 3: The Parse step
This is where you tell Claude how to handle the input. Granola transcripts have a specific shape: timestamped speaker turns, sometimes with Granola's own AI summary at the top. The skill needs to handle both the raw transcript and any pre-existing summary.
## Step 1: Parse
Read the transcript the user provides. It may be:
- Pasted text from Granola (with or without Granola's AI summary)
- An uploaded file (.txt, .md, .pdf)
- Raw notes the user typed during the meeting
**Extract:**
- **Attendees**: Who was in the meeting (from speaker labels or mentions)
- **Duration**: If timestamps are present, calculate meeting length
- **Meeting type**: Classify from content:
- `standup`: Short, status-focused, multiple people giving updates
- `1:1`: Two people, often manager/report or cross-functional
- `planning`: Feature/sprint/project planning, estimating work
- `decision`: Focused on reaching a specific decision
- `review`: Retrospective, post-mortem, design review, demo
- `external`: Customer, vendor, partner, interview
- **Raw topics**: What was discussed, in order
If Granola's AI summary is present, use it as a cross-reference but
always go back to the transcript for details. The AI summary often
misses nuance, attribution, and qualifying statements.
**Ask the user** (only if the meeting type is ambiguous):
Use AskUserQuestion:
Question: "What kind of meeting was this?"
Options:
- "Standup/sync": "Quick status updates"
- "1:1": "Two-person check-in"
- "Planning": "Scoping or estimating work"
- "Decision meeting": "Needed to reach a specific conclusion"
- "Review/retro": "Looking back at work done"
- "External": "Customer, vendor, or partner call"
A few things to notice:
Explicit input formats. The skill lists exactly what forms the input might take. Claude doesn't have to guess whether pasted text is a transcript or something else.
Classification. Meeting type drives how the notes get structured in Step 2. A standup needs per-person updates; a decision meeting needs the decision front and center. Classifying early means the rest of the workflow adapts.
AskUserQuestion. This is the skill's UX primitive. Instead of Claude guessing, it pauses and asks, but only when the answer is genuinely ambiguous. Don't ask when the transcript clearly says "daily standup." The "only if ambiguous" qualifier matters.
Trust but verify. The instruction about Granola's AI summary ("use it as a cross-reference but always go back to the transcript") is the kind of guardrail that prevents subtle quality problems. Without it, Claude might just reformat Granola's summary and call it done.
Step 4: The Structure step
Now Claude has parsed the transcript. This step organizes it into a consistent format, adapted by meeting type.
## Step 2: Structure
Organize the parsed content into structured notes. The format adapts
based on meeting type, but every meeting gets these sections:
### Universal sections (all meeting types)
- **Summary**: 2-3 sentences. What was this meeting about and what
was the outcome? Lead with the most important result.
- **Decisions**: Bulleted list. Each decision states what was decided,
who made the call, and any conditions/caveats. If no decisions were
made, say "No decisions reached": don't omit the section.
- **Action items**: Bulleted list. Each item has: owner (name),
action (specific and concrete), deadline (if mentioned, otherwise
"TBD"). Format: `@owner: action (by deadline)`
- **Key discussion points**: The 3-5 most important topics discussed,
with enough context that someone who wasn't in the meeting
understands the substance, not just the topic name.
### Meeting-type adaptations
**Standup/sync:**
- Add a "Per-person updates" section before Key discussion points
- Each person gets: what they shared, any blockers mentioned
- Keep it brief: standups are status, not discussion
**1:1:**
- Add a "Follow-ups from last time" section if prior items are referenced
- Flag any career/growth/feedback topics separately under "Development"
- These notes are often sensitive: note this in the output
**Planning:**
- Add an "Estimates/scope" section with what was scoped and rough sizing
- Add an "Open questions" section for unresolved items
- Flag any scope changes or cuts that were discussed
**Decision meeting:**
- Lead the summary with the decision
- Add an "Alternatives considered" section
- Add a "Dissent/concerns" section: capture who pushed back and why
**Review/retro:**
- Add "What went well" and "What to improve" sections
- Add "Changes for next time" with concrete commitments
**External:**
- Add "Their asks" and "Our commitments" sections
- Flag any follow-up promises made to the external party
- These often need careful tone: keep notes factual, not interpretive
This is the core of the skill. A few design choices worth calling out:
Universal sections first, adaptations second. Every meeting produces decisions, action items, and discussion points. The meeting-type adaptations add extra sections without replacing the universal ones. The output is always consistent at its core, with relevant additions on top.
"Don't omit the section." The instruction for decisions ("if no decisions were made, say 'No decisions reached'") prevents a common failure mode. Without it, Claude omits the Decisions section when there are none, and the reader can't tell if no decisions were made or if the notes are incomplete.
Specific formatting for action items. The @owner: action (by deadline) format is opinionated. It's designed to be scannable in Slack and greppable later. The @ prefix makes it easy to find your name.
Sensitivity flags. The 1:1 and external meeting notes get explicit callouts about tone and sensitivity. This is the kind of guardrail that only matters 10% of the time but prevents real problems when it does.
Step 5: The Deliver step
The final step formats the output for Slack and presents it to the user.
## Step 3: Deliver
Format the structured notes for Slack and present to the user.
### Slack formatting rules
- Use Slack's mrkdwn syntax, not standard markdown:
- Bold: `*text*` (single asterisk, not double)
- Italic: `_text_` (underscores)
- Bulleted lists: `•` or `-` both work
- Code: backticks work the same
- Links: `<url|display text>`
- User mentions: use `@name` (the user will replace with actual Slack handles)
- Block quotes: `>` works the same
- No headers: Slack doesn't render `#`. Use `*bold text*` on its own line instead
- Keep the total length under ~4000 characters for a single Slack message.
If longer, split into two messages: "Summary + Decisions + Action Items"
in the first, "Discussion + Details" in the second. Tell the user where
the split is.
- Do not use code blocks for the entire message. Use them only for
actual code, commands, or structured data.
### Output format
Present the formatted notes in a single code block so the user can
copy-paste directly into Slack:
*Meeting Notes: [meeting name/topic]*
_[date] · [duration] · [attendees]_
*Summary*
[2-3 sentence summary]
*Decisions*
• [decision 1]
• [decision 2]
*Action Items*
• @owner: action (by deadline)
• @owner: action (by deadline)
*Key Discussion*
• [point 1 with context]
• [point 2 with context]
After presenting the notes, ask:
Use AskUserQuestion:
Question: "How do these look?"
Options:
- "Ship it": "Post as-is"
- "Edit first": "I want to adjust some details"
- "Too long": "Condense further"
- "Add context": "I want to add something that was missed"
Slack mrkdwn, not markdown. This is the kind of detail that makes the skill actually useful versus merely correct. Standard markdown headers (## Decisions) render as plain text in Slack. The skill specifies Slack's actual formatting syntax so the output works on paste without editing.
Character limit awareness. Slack messages have practical length limits. Rather than hoping the output fits, the skill tells Claude to split long notes and explain the split. This prevents the user from pasting a message that gets truncated.
Copy-paste ready. The output goes in a code block so the user can select, copy, and paste into Slack directly. No reformatting needed.
One final checkpoint. The AskUserQuestion at the end gives the user a chance to adjust before posting. "Too long" and "Add context" are the two most common feedback types: having them as explicit options makes the feedback loop fast.
The complete skill
Here's the full SKILL.md, all ~80 lines:
---
name: meeting-notes
description: >
Parse meeting transcripts (Granola, Otter, or raw notes) into structured
notes with decisions, action items, and key discussion points.
Format for Slack delivery. Triggers on: meeting notes, transcript,
standup notes, meeting summary, action items from meeting.
---
# Meeting Notes
Parse meeting transcripts into structured, Slack-ready notes.
## Workflow
1. **Parse**: Read the transcript, identify meeting type, extract raw content
2. **Structure**: Organize by decisions, action items, discussion points
3. **Deliver**: Format for Slack and present to user
## Step 1: Parse
Read the transcript the user provides. It may be:
- Pasted text from Granola (with or without Granola's AI summary)
- An uploaded file (.txt, .md, .pdf)
- Raw notes the user typed during the meeting
**Extract:**
- **Attendees**: Who was in the meeting (from speaker labels or mentions)
- **Duration**: If timestamps are present, calculate meeting length
- **Meeting type**: Classify from content:
- `standup`: Short, status-focused, multiple people giving updates
- `1:1`: Two people, often manager/report or cross-functional
- `planning`: Feature/sprint/project planning, estimating work
- `decision`: Focused on reaching a specific decision
- `review`: Retrospective, post-mortem, design review, demo
- `external`: Customer, vendor, partner, interview
- **Raw topics**: What was discussed, in order
If Granola's AI summary is present, use it as a cross-reference but
always go back to the transcript for details. The AI summary often
misses nuance, attribution, and qualifying statements.
**Ask the user** (only if the meeting type is ambiguous):
Use AskUserQuestion:
Question: "What kind of meeting was this?"
Options:
- "Standup/sync": "Quick status updates"
- "1:1": "Two-person check-in"
- "Planning": "Scoping or estimating work"
- "Decision meeting": "Needed to reach a specific conclusion"
- "Review/retro": "Looking back at work done"
- "External": "Customer, vendor, or partner call"
## Step 2: Structure
Organize the parsed content into structured notes. The format adapts
based on meeting type, but every meeting gets these sections:
**Summary**: 2-3 sentences. What was this meeting about and what
was the outcome? Lead with the most important result.
**Decisions**: Bulleted list. Each decision states what was decided,
who made the call, and any conditions/caveats. If no decisions were
made, say "No decisions reached": don't omit the section.
**Action items**: Bulleted list. Each item has: owner, action, deadline.
Format: `@owner: action (by deadline)`
**Key discussion points**: The 3-5 most important topics discussed,
with enough context that someone who wasn't there understands the
substance, not just the topic name.
### Meeting-type additions
- **Standup:** Add per-person updates before discussion points
- **1:1:** Add "Follow-ups from last time" and flag career/feedback topics
- **Planning:** Add "Estimates/scope" and "Open questions"
- **Decision:** Lead summary with the decision, add "Alternatives considered"
- **Review:** Add "What went well," "What to improve," "Changes for next time"
- **External:** Add "Their asks" and "Our commitments"
## Step 3: Deliver
Format for Slack using mrkdwn (not standard markdown):
- Bold: `*text*` (single asterisk)
- No `#` headers: use `*bold text*` on its own line
- Action items: `• @owner: action (by deadline)`
- Keep under ~4000 characters. If longer, split into two messages.
Present in a code block for direct copy-paste:
*Meeting Notes: [topic]*
_[date] · [duration] · [attendees]_
*Summary*
[summary]
*Decisions*
• [decision]
*Action Items*
• @owner: action (by deadline)
*Key Discussion*
• [point with context]
After presenting, ask:
"How do these look?"
Options: "Ship it" / "Edit first" / "Too long" / "Add context"
Setting it up
The skill file goes in your project directory. Two placement options:
Option A: Project-level skill. Create a directory in your project:
your-project/
meeting-notes/
SKILL.md ← the file above
Claude Code picks it up automatically when you work in that project. The skill triggers when your message matches the description keywords.
Option B: User-level skill. Put it in ~/.claude/skills/meeting-notes/SKILL.md to make it available across all projects. Same file, broader scope.
To use it, paste a Granola transcript into Claude Code and say something that matches: "turn this into meeting notes," "summarize this standup," "pull out the action items from this call." Claude reads the SKILL.md, follows the three steps, and produces Slack-formatted notes. The whole cycle takes about 30 seconds for a typical 30-minute meeting transcript.
What makes a good skill
Building this skill surfaces a few principles that apply broadly.
Be opinionated about output format
The Slack mrkdwn formatting rules, the @owner: action (by deadline) convention, the "don't omit empty sections" instruction: these are opinions. They make the output consistent and immediately useful. A skill that says "format appropriately" produces different output every time.
Ask only when it matters
The skill has two AskUserQuestion checkpoints: meeting type (only if ambiguous) and final review. Not zero, not five. Every question interrupts the user's flow. Ask when Claude genuinely can't determine the answer from the input, and at the end for approval. Everything else, just decide.
Handle the actual output medium
This skill isn't "generate meeting notes." It's "generate meeting notes for Slack." That last part, the mrkdwn formatting, the character limit, the copy-paste code block, is what makes it actually useful versus a demo. If your skill produces output that needs manual reformatting before the user can use it, the skill is incomplete.
Name the failure modes
"If no decisions were made, say 'No decisions reached.'" "If Granola's AI summary is present, go back to the transcript." "If longer than 4000 characters, split." These instructions exist because without them, Claude will do the wrong thing some percentage of the time. Each one is a bug fix written in advance.
Evolving the skill
After using this skill on 10-20 meetings, you'll find gaps, just like the portfolio analysis skill went from 756 to 942 lines after real usage. Common evolutions:
- Team-specific conventions. Your team uses specific Slack channels for different meeting types. Add routing guidance.
- Recurring meeting memory. For weekly syncs, you want "changes since last week." Add instructions for Claude to reference the previous notes.
- Jira/Linear integration. Action items could create tickets directly. Add an MCP server and a step for ticket creation.
- Multiple output formats. Sometimes you want Slack, sometimes email, sometimes a Notion page. Add a format selection to the Deliver step.
The point of a skill is that these evolutions happen in one place, the SKILL.md file, and every future use gets the improvement. You're not re-prompting. You're building institutional knowledge.
This was the simple skill: 80 lines, 3 steps, no reference files, no external tools. Part 2 will cover what happens when skills get complex: multi-step workflows, reference data files, external tooling, and the gaps that only surface when you run the skill on real data.
Related writing
How a Diffusion Model Works: A Practitioner's Read of the 2026 Image Stack
Modern image models aren't U-Nets running 50 denoising steps. They're transformers running 4 steps of a straight-line flow. Once that lands, every product surface starts making sense.
How a Vision LLM Works: A Practitioner's Read of the 2026 Multimodal Stack
Vision LLMs don't see images. They tokenize them. Once that lands, the cost, the failure modes, and the design space all fall out cleanly.
How a Video Model Works: A Practitioner's Read of Veo 3.1 and Seedance 2.0
Modern video models aren't image models in a loop. They're diffusion transformers on spatiotemporal patches, with audio now riding the same train.