Deploying a Custom OpenClaw Skill: Best Practices

Clawpedia · For Humans

Learn deployment strategies and best practices for shipping reliable OpenClaw skills to production.

Overview

You've written and tested a custom skill — now it's time to make it run reliably in production. This article covers best practices for deployment, versioning, error handling, performance optimization, and monitoring that separate a hobby project from a production-grade skill.

Pre-Deployment Checklist

Before deploying, verify these essentials:

CheckCommandWhy
Manifest validopenclaw skills validate my-skillCatches YAML errors
Tests passopenclaw skills test my-skill --unitPrevents regressions
Permissions minimalReview manifest.yamlSecurity principle of least privilege
Dependencies pinnedCheck requirements.txt or package.jsonReproducible builds
Error handlingManual reviewGraceful failures
Timeouts setCheck runtime.timeoutPrevent hanging

Versioning

API keys securedCheck config, not hardcodedSecurity

Use Semantic Versioning (SemVer) for your skills:


MAJOR.MINOR.PATCH

1.0.0 → 1.0.1  (patch: bug fix)
1.0.1 → 1.1.0  (minor: new feature, backward compatible)
1.1.0 → 2.0.0  (major: breaking change)

Update the version in manifest.yaml before each release:


name: my-skill
version: 1.2.0    # Bumped from 1.1.0

Maintain a CHANGELOG.md:


# Changelog

## [1.2.0] - 2024-01-15
### Added
- Support for multiple languages
- Caching for repeated queries

### Fixed
- Timeout on slow API responses

## [1.1.0] - 2024-01-10
### Added
- Error details in failure messages

Error Handling

Production skills must handle every failure gracefully:


import { Skill, SkillContext, SkillResult } from "@openclaw/sdk";

export default class ProductionSkill extends Skill {
  async execute(context: SkillContext): Promise<SkillResult> {
    const param = context.extractParam("query");

    // Validate input
    if (!param || param.length > 500) {
      return this.error("Please provide a valid query (max 500 characters).");
    }

    try {
      const response = await this.fetchWithTimeout(
        `https://api.example.com/data?q=${encodeURIComponent(param)}`,
        { timeout: 8000 }
      );

      if (!response.ok) {
        // Handle specific HTTP errors
        switch (response.status) {
          case 401:
            this.log.error("API key invalid or expired");
            return this.error("Service authentication failed. Please check configuration.");
          case 429:
            return this.error("Rate limit reached. Please try again in a minute.");
          case 503:
            return this.error("Service is temporarily unavailable. Please try again later.");
          default:
            return this.error(`Service returned an error (${response.status}).`);
        }
      }

      const data = await response.json();
      return this.success(this.formatResult(data));

    } catch (err) {
      if (err.name === "AbortError") {
        return this.error("Request timed out. The service may be slow.");
      }
      this.log.error("Unexpected error", { error: err.message });
      return this.error("Something went wrong. Please try again.");
    }
  }

  private async fetchWithTimeout(url: string, opts: { timeout: number }) {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), opts.timeout);
    try {
      return await fetch(url, { signal: controller.signal });
    } finally {
      clearTimeout(id);
    }
  }

  private formatResult(data: any): string {
    // Format logic here
    return `Result: ${JSON.stringify(data)}`;
  }
}

Error Handling Rules

Caching

Reduce API calls and improve response time with caching:


import { Skill, SkillContext, SkillResult, Cache } from "@openclaw/sdk";

export default class CachedSkill extends Skill {
  private cache = new Cache({
    ttl: 300,          // 5 minutes
    maxSize: 1000,     // Max 1000 entries
  });

  async execute(context: SkillContext): Promise<SkillResult> {
    const query = context.extractParam("query");
    const cacheKey = `result:${query}`;

    // Check cache first
    const cached = this.cache.get(cacheKey);
    if (cached) {
      return this.success(cached, { source: "cache" });
    }

    // Fetch fresh data
    const result = await this.fetchData(query);
    
    // Store in cache
    this.cache.set(cacheKey, result);
    
    return this.success(result, { source: "api" });
  }
}

Rate Limiting

Protect external APIs from excessive calls:


# manifest.yaml
runtime:
  rate_limit:
    max_calls: 60
    per_seconds: 60
    strategy: sliding_window    # sliding_window | fixed_window

Deployment Options

Local Deployment

Simplest approach — the skill lives in your OpenClaw skills directory:


# Skills directory
~/.openclaw/skills/my-skill/

# Hot-reload after changes
openclaw skills reload my-skill

Git-Based Deployment

Use Git for version control and deployment:


# Initialize a Git repo for your skill
cd ~/.openclaw/skills/my-skill
git init
git add .
git commit -m "v1.0.0: Initial release"

# Push to remote
git remote add origin git@github.com:user/my-openclaw-skill.git
git push -u origin main

On the target machine:


cd ~/.openclaw/skills/
git clone git@github.com:user/my-openclaw-skill.git my-skill
openclaw skills reload my-skill

ClawHub Publication

For sharing with the community:


openclaw skills publish my-skill

See Publishing Your Skill to the Community Skill Registry for details.

Monitoring in Production

Health Checks

Add a health check method to your skill:


export default class MySkill extends Skill {
  async healthCheck(): Promise<boolean> {
    try {
      const resp = await fetch("https://api.example.com/health");
      return resp.ok;
    } catch {
      return false;
    }
  }
}

# Check skill health
openclaw skills health my-skill

Performance Metrics


# View skill performance
openclaw skills stats my-skill

# Output:
# Calls (24h):     342
# Avg Latency:     1.2s
# P95 Latency:     2.8s
# Success Rate:    98.5%
# Cache Hit Rate:  67.2%
# Errors:          5

Alerting

Configure alerts for skill failures:


skills:
  my-skill:
    alerts:
      on_error:
        threshold: 5           # Alert after 5 errors in...
        window: 300            # ...5 minutes
        notify: telegram       # Send alert to Telegram
      on_latency:
        threshold: 5000        # Alert if latency exceeds 5s
        notify: slack

Security Best Practices


# Audit skill dependencies
openclaw skills audit my-skill

Troubleshooting

Skill Works Locally but Fails in Production

High Latency in Production

Next Steps

Related Articles