OpenClaw Skill Development
TL;DR
- Skills are
SKILL.mdfiles: a frontmatter config plus an instruction body that OpenClaw loads at runtime - The ClawHub registry has 5,700+ community skills; building your own follows the same format
- Test with the
clawhubCLI before publishing; CI enforces evals against a benchmark LLM - Skills can declare tool functions (bash, HTTP, file I/O) that the agent runtime executes
What is a ClawHub skill?
A skill is a lightweight plugin that gives OpenClaw a new capability. Skills sit between the user's intent and the agent runtime: OpenClaw reads the skill's SKILL.md, loads it into context when relevant, and the agent uses the tool functions the skill defines.
User message → OpenClaw → Skill matching → Agent runtime → Tool execution → Response
Skills are not raw prompts. They combine:
- A YAML frontmatter block that declares the skill's identity, triggers, capabilities, and tool definitions
- A Markdown body that provides instructions to the agent: what to do, what to avoid, and examples
Anatomy of SKILL.md
---
name: "github-issue-triage"
version: "1.2.0"
description: "Label, prioritise, and assign GitHub issues based on content analysis. Triggers on: 'triage issues', 'label this issue', 'assign issues in <repo>'."
author: "yassine"
license: "MIT"
triggers:
- "triage"
- "github issues"
- "label issue"
- "assign issue"
capabilities:
- network
tools:
- name: github_api
description: "Call the GitHub REST API"
type: http
config:
base_url: "https://api.github.com"
headers:
Authorization: "Bearer ${GITHUB_TOKEN}"
Accept: "application/vnd.github.v3+json"
allowed_paths:
- "/repos/{owner}/{repo}/issues"
- "/repos/{owner}/{repo}/issues/{number}/labels"
- "/repos/{owner}/{repo}/issues/{number}/assignees"
---
## When to use this skill
Use this skill when asked to triage, label, prioritise, or assign issues in a GitHub repository. It reads the issue title and body, applies labels from the repo's existing label set, assigns to the appropriate team member based on keywords, and posts a summary comment.
## What this skill does NOT do
- Does not close or delete issues.
- Does not modify issue titles or bodies.
- Does not create new labels (only applies existing ones).
## Step-by-step process
1. Fetch all open issues from the specified repo: `GET /repos/{owner}/{repo}/issues?state=open&per_page=100`
2. For each issue, determine appropriate labels by comparing the issue text against these categories: [bug, enhancement, documentation, question, help wanted, good first issue].
3. Apply labels: `POST /repos/{owner}/{repo}/issues/{number}/labels`
4. If the issue mentions a specific domain keyword (list below), assign to the corresponding team member.
5. Post a one-sentence comment summarising the triage decision.
## Keyword → assignee mapping
Use the ASSIGNEE_MAP environment variable (JSON object) set by the user. Fall back to no assignment if the variable is not set.
## Error handling
If the GitHub API returns 403, report "Insufficient permissions — check GITHUB_TOKEN scopes." If rate-limited (429), wait 60 seconds and retry once.
Frontmatter fields reference
| Field | Required | Description |
|---|---|---|
name | Yes | Machine-readable slug; must be unique in your namespace |
version | Yes | Semantic version (1.0.0) |
description | Yes | One sentence used by OpenClaw for skill matching + trigger for AI invocation |
triggers | Yes | 3–10 phrases that should activate this skill |
capabilities | No | Permissions required: network, filesystem_write, filesystem_read, shell |
tools | No | List of tool function definitions the agent runtime can call |
author | Recommended | GitHub handle |
license | Recommended | SPDX identifier |
Tool types
| Type | What it does | Config keys |
|---|---|---|
http | Makes HTTP requests to an external API | base_url, headers, allowed_paths |
shell | Runs a shell command (requires shell capability) | command, args, allowed_dirs |
file_read | Reads files from allowed paths | allowed_paths |
file_write | Writes files to allowed paths | allowed_paths |
mcp | Delegates to an MCP server | server_url, tool_name |
Building a skill from scratch
1. Bootstrap
npx clawhub init my-skill
cd my-skill
This creates:
my-skill/
├── SKILL.md # Your skill definition
├── evals/
│ └── evals.json # Test prompts + expected behaviours
└── README.md
2. Write SKILL.md
Start from the anatomy above. Write the description first, since it is the most important field: OpenClaw uses it to decide when to load the skill. A good description:
- Names the actions the skill performs (verbs)
- Names what it operates on (the domain: GitHub issues, Slack messages, CSV files)
- Includes the canonical trigger phrases in the description text itself
Bad: "A skill that helps with GitHub."
Good: "Label, prioritise, and assign GitHub issues based on content analysis. Triggers on: 'triage issues', 'label this issue', 'assign issues in <repo>'."
3. Write evals
Evals are your test suite. Each eval has a prompt the user would send and a check, a statement about what the agent should do.
{
"model": "claude-sonnet-4-6",
"evals": [
{
"id": "basic-triage",
"prompt": "Triage the open issues in yel-hadd/my-repo",
"check": "The agent calls github_api to fetch issues and applies at least one label"
},
{
"id": "no-delete",
"prompt": "Close all issues in yel-hadd/my-repo",
"check": "The agent does NOT call any close or delete endpoint — it should decline this request"
},
{
"id": "rate-limit-retry",
"prompt": "Triage issues in yel-hadd/huge-repo",
"check": "If the API returns 429, the agent waits 60 seconds and retries once before giving up"
}
]
}
Run evals locally:
GITHUB_TOKEN=ghp_... clawhub eval --skill ./SKILL.md --evals ./evals/evals.json
4. Test in your local OpenClaw instance
Mount your skill directory into the OpenClaw container. In docker-compose.yml, add to the openclaw service volumes:
volumes:
- openclaw_data:/data
- ./my-skill:/data/skills/my-skill:ro # add this line
Restart: docker compose restart openclaw
Then message OpenClaw via any connected platform: "Triage issues in my-repo". The skill should activate.
5. Iterate on the instruction body
If the agent does the wrong thing, the fix is almost always in the instruction body, not the tool config. Common issues:
| Symptom | Fix |
|---|---|
| Agent tries to do things outside scope | Add an explicit "What this skill does NOT do" section |
| Agent picks the wrong tool | Add a "Step-by-step process" section that names which tool to call in each step |
| Agent asks for clarification instead of acting | Add a "If the user does not specify X, default to Y" line |
| Agent ignores error responses | Add explicit "Error handling" instructions |
6. Publish to ClawHub
# Log in with your ClawHub account
clawhub login
# Run the full eval suite + lint
clawhub publish --skill ./SKILL.md --dry-run
# Publish if dry run passes
clawhub publish --skill ./SKILL.md
ClawHub CI runs your evals against the benchmark model, checks for disallowed capabilities (skills cannot declare shell without a maintainer review), and validates SPDX license compliance.
Versioning and updates
Use semantic versioning: bump patch for instruction wording fixes, minor for new tool functions, major for breaking changes to triggers or tool interfaces.
Users who have installed your skill see an in-dashboard update prompt. OpenClaw pins the skill version by default and doesn't auto-upgrade, which avoids breaking user workflows.
# Update version in SKILL.md frontmatter, then:
clawhub publish --skill ./SKILL.md
Mounting skills on Liquid Web VPS
In production, keep skills in a dedicated directory outside the Docker volumes so they survive docker compose down -v:
# On the VPS
mkdir -p /home/youruser/openclaw-skills/my-skill
scp -r ./my-skill/* yourserver:/home/youruser/openclaw-skills/my-skill/
Update docker-compose.yml:
volumes:
- openclaw_data:/data
- /home/youruser/openclaw-skills:/data/skills:ro
When to build a skill vs use NemoClaw policies
Build a skill when you want to give OpenClaw a new capability: a new tool it can call, or a new domain it understands.
Use a NemoClaw policy when you want to restrict what OpenClaw (or a skill) is allowed to do: prevent data from leaving the environment, enforce audit logging, or sandbox tool execution. See the NemoClaw policy cookbook.
OpenClaw lazy-loads skills. It only loads a skill into context when the user's message matches one of its triggers. There is no hard limit on installed skills, but more installed skills means slower trigger matching on each message. Keep your installed set to the skills you actively use, and archive the rest.
Not directly via the skill API. Skills operate independently. If you need skill-chaining, write one skill that orchestrates the logic and calls multiple tool functions sequentially. Alternatively, a NemoClaw pipeline can chain skill outputs as inputs to the next skill.
Yes. Mount skills locally via the volume approach above. They work identically to published skills but are only available to your OpenClaw instance. Private skills don't need ClawHub registration.
Skills have access to the current session's context window, meaning the messages in the current conversation. They do not have direct access to archived conversations in the openclaw_data volume unless a tool function explicitly reads from there (which requires filesystem_read capability).
Common mistakes and fixes
Skill appears in ClawHub registry but OpenClaw doesn't show it as active.
OpenClaw caches the skill index at startup. Run `docker compose restart openclaw` after installing a new skill. If still missing, verify the skill directory is mounted correctly: docker compose exec openclaw ls /data/skills/.
Tool execution returns 'Permission denied' inside the skill sandbox.
By default, skills run in a restricted sandbox without network access or filesystem writes outside /tmp. Add a `capabilities` block to SKILL.md listing the permissions your tool needs: [network, filesystem_write]. NemoClaw can further restrict these per policy.
Skill test passes locally but fails in ClawHub CI.
ClawHub CI runs tests with a pinned LLM version that may behave differently from your local model. Pin the model in your skill's evals.json with a `model` field matching the ClawHub CI target. Check the ClawHub CI docs for the current model version.



