claw-doctor

Diagnose and fix common OpenClaw / NanoClaw issues — broken skills, missing scripts, API key failures, path resolution bugs, and configuration problems. The meta-skill for when your claw is broken.

install
source · Clone the upstream repo
git clone https://github.com/tianyilt/openclaw-repair-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tianyilt/openclaw-repair-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/claw-doctor" ~/.claude/skills/tianyilt-openclaw-repair-skills-claw-doctor && rm -rf "$T"
OpenClaw · Install into ~/.openclaw/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tianyilt/openclaw-repair-skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/claw-doctor" ~/.openclaw/skills/tianyilt-openclaw-repair-skills-claw-doctor && rm -rf "$T"
manifest: skills/claw-doctor/SKILL.md
source content

Claw Doctor — OpenClaw Self-Repair Skill

This skill diagnoses and fixes common OpenClaw / NanoClaw problems. When something breaks, run through the checklist below before giving up.


When to Activate

Activate this skill when the user reports any of the following:

  • "skill not found" / skill does not trigger
  • "No such file or directory" when running a skill script
  • API key errors from a skill
  • Skill produces no output or wrong output
  • "SKILL.md is invalid" / YAML parse errors
  • A skill works globally but not in workspace (or vice versa)
  • Skills were working before but stopped after an update

Skill Load Order & Paths

OpenClaw loads skills from two locations. Workspace skills take priority.

Priority 1 (high): <workspace>/skills/<skill-name>/SKILL.md
Priority 2 (low):  ~/.openclaw/skills/<skill-name>/SKILL.md

Diagnostic: list all loaded skills

# Show what's installed globally
ls ~/.openclaw/skills/ 2>/dev/null || echo "No global skills dir"

# Show what's installed in current workspace
ls ./skills/ 2>/dev/null || echo "No workspace skills dir"

If a skill appears in both, the workspace version wins — check for version mismatches.


Problem 1 — Skill Does Not Trigger

Symptom: User invokes a skill but Claude ignores it or does not follow the skill procedure.

Diagnosis checklist:

  1. Is the

    SKILL.md
    actually present and readable?

    cat ./skills/<skill-name>/SKILL.md | head -20
    
  2. Does the frontmatter YAML parse correctly?

    python3 -c "
    import re, sys
    txt = open('skills/<skill-name>/SKILL.md').read()
    fm = re.search(r'^---\n(.*?)\n---', txt, re.DOTALL)
    print('Frontmatter found:', bool(fm))
    if fm:
        import yaml; d = yaml.safe_load(fm.group(1)); print('Keys:', list(d.keys()))
    "
    
  3. Do the

    keywords
    in the SKILL.md match what the user said?

    • Add more keyword variants (synonyms, abbreviations) if not matching.
  4. Is

    description
    clear enough for the model to identify the skill?

    • Short, specific descriptions outperform vague ones.

Fix: Ensure frontmatter is valid YAML (no tabs, proper quoting, correct indentation).


Problem 2 — Script Not Found (Path Resolution)

Symptom:

bash: scripts/run: No such file or directory

This is the most common skill bug. Scripts referenced in SKILL.md as

scripts/foo
are relative to the skill's installation directory inside the OpenClaw plugin cache, not the current working directory.

Canonical fix — resolve the script path dynamically before every use:

# For a skill named "my-skill" with a script named "run"
MY_SCRIPT=$(find ~/.openclaw -name "run" -path "*/my-skill/*/scripts/*" -type f 2>/dev/null | head -1)
# Fallback: check workspace skills
[ -z "$MY_SCRIPT" ] && MY_SCRIPT=$(find ./skills -name "run" -path "*/my-skill/scripts/*" -type f 2>/dev/null | head -1)

if [ -z "$MY_SCRIPT" ]; then
  echo "ERROR: script not found. Is the skill installed?"
  exit 1
fi

$MY_SCRIPT <args>

Also check: Is the script executable?

chmod +x ~/.openclaw/skills/my-skill/scripts/*
chmod +x ./skills/my-skill/scripts/*

Problem 3 — API Key Not Configured

Symptom: Skill returns

{"success": false, "setup_required": true}
or 401/403 errors.

Standard API key setup flow:

  1. The skill's SKILL.md should document where the key is stored (usually
    ~/.openclaw/secrets/<skill-name>.key
    or an env var).
  2. Check if the key file exists:
    ls ~/.openclaw/secrets/ 2>/dev/null || echo "No secrets dir"
    
  3. Run the skill's setup command (usually
    scripts/run setup <api-key>
    ).
  4. Verify the key was saved:
    cat ~/.openclaw/secrets/<skill-name>.key 2>/dev/null | head -c 20
    

If no setup command exists, ask the user to set the env var directly:

export SKILL_API_KEY="their-key-here"
# Add to ~/.zshrc or ~/.bashrc for persistence

Problem 4 — Dependency Missing (Node.js / Python / tool)

Symptom:

node: command not found
,
python3: No such file or directory
,
jq: command not found

Quick dependency check:

echo "=== Core deps ===" && \
node --version 2>/dev/null || echo "MISSING: node" && \
python3 --version 2>/dev/null || echo "MISSING: python3" && \
jq --version 2>/dev/null || echo "MISSING: jq" && \
curl --version 2>/dev/null | head -1 || echo "MISSING: curl"

Fix by platform:

ToolmacOSUbuntu/Debian
Node.js
brew install node
apt install nodejs npm
Python 3
brew install python3
apt install python3
jq
brew install jq
apt install jq

For Python skill dependencies:

pip3 install -r skills/<skill-name>/requirements.txt 2>/dev/null \
  || echo "No requirements.txt found"

For Node skill dependencies:

cd skills/<skill-name> && npm install 2>/dev/null \
  || echo "No package.json found"

Problem 5 — YAML Frontmatter Errors

Symptom: Skill loads but metadata is wrong / keywords not indexed.

Valid frontmatter template:

---
name: my-skill-name         # lowercase, hyphens only
description: One clear sentence describing what this skill does.
keywords:
  - keyword-one
  - keyword-two
license: MIT
---

Common mistakes:

MistakeFix
Tabs instead of spacesReplace with 2-space indentation
Unquoted
:
in description
Wrap value in quotes
Missing
name
field
Add it — it's required
keywords
as inline list
[a, b]
Use block list
- a\n- b

Validate:

python3 -c "
import yaml, sys
txt = open('skills/<skill-name>/SKILL.md').read().split('---')[1]
try:
    d = yaml.safe_load(txt)
    print('OK:', d)
except yaml.YAMLError as e:
    print('YAML ERROR:', e)
    sys.exit(1)
"

Problem 6 — Skill Worked Before, Broke After Update

Symptom: OpenClaw updated and a skill stopped working.

Checklist:

  1. Check if the OpenClaw plugin cache was cleared:

    ls ~/.openclaw/plugins/cache/ 2>/dev/null | head -10
    
  2. Reinstall the skill from source:

    # From a cloned skills repo
    cp -r /path/to/skills-repo/skills/<skill-name> ~/.openclaw/skills/
    
  3. Check for breaking changes in OpenClaw's skill API — look at the OpenClaw changelog for

    SKILL.md
    format updates.

  4. Test the script directly (bypassing OpenClaw):

    bash skills/<skill-name>/scripts/<main-script> --help
    

Full Health Check

Run this to get a complete snapshot of the OpenClaw environment:

echo "=== OpenClaw Health Check ===" && \
echo "--- Global skills ---" && ls ~/.openclaw/skills/ 2>/dev/null || echo "(none)" && \
echo "--- Workspace skills ---" && ls ./skills/ 2>/dev/null || echo "(none)" && \
echo "--- Secrets ---" && ls ~/.openclaw/secrets/ 2>/dev/null || echo "(none)" && \
echo "--- Plugin cache ---" && ls ~/.openclaw/plugins/cache/ 2>/dev/null | head -5 || echo "(empty)" && \
echo "--- Dependencies ---" && \
  node --version 2>/dev/null && \
  python3 --version 2>/dev/null && \
  jq --version 2>/dev/null && \
echo "=== Done ==="

For Claude Code Users

This skill also works as a Claude Code user-invocable skill. Add the following to

~/.claude/CLAUDE.md
under
## User-Invocable Skills
:

### claw-doctor

Diagnose and fix OpenClaw / NanoClaw problems.

**Trigger**: user mentions skill not loading, script not found, API key error, SKILL.md broken, OpenClaw not working

**Procedure**: Follow the claw-doctor SKILL.md checklist:
1. Identify symptom category (trigger / script path / API key / dependency / YAML / post-update)
2. Run the relevant diagnostic commands
3. Apply the fix and verify with the Full Health Check

Problem 7 — 飞书 Bot 用了错误的模型

症状:飞书里回复的模型和

openclaw.json
配置的不一致,或
/new
之后还是老模型。

根因 1 — Session 里的 model override 覆盖了 agent 配置

Session 文件会持久化上一次手动切换的模型,重启后也不会清除。

# 找出哪个 session 有 model override
python3 -c "
import json
for agent in ['main', 'sii']:
    path = f'/Users/tianyiliang/.openclaw/agents/{agent}/sessions/sessions.json'  # 按实际路径调整
    try:
        data = json.load(open(path))
        for k, v in data.items():
            if v.get('model'):
                print(f'[{agent}] {k}: model={v[\"model\"]}')
    except: pass
"

修复:删掉对应 session 里的

model
/
modelProvider
字段,或直接删掉整个 session 条目,再重启 gateway:

openclaw gateway restart

根因 2 —

main
agent 不在
agents.list
,binding 被忽略

bindings
里写了
"agentId": "main"
但不生效——因为
main
是内置 agent,不在
agents.list
里就不能作为路由目标。

诊断

openclaw agents list --bindings   # 看 main 是否出现

修复:把

main
显式加入
agents.list

python3 -c "
import json
path = '~/.openclaw/openclaw.json'  # 按实际路径
cfg = json.load(open(path))
if not any(a['id'] == 'main' for a in cfg['agents']['list']):
    cfg['agents']['list'].insert(0, {
        'id': 'main',
        'name': 'main',
        'workspace': cfg['agents']['defaults']['workspace'],
        'model': {'primary': 'custom-local/gemini-3-pro-preview', 'fallbacks': []}
    })
    json.dump(cfg, open(path,'w'), indent=2, ensure_ascii=False)
    print('Added main to agents.list')
"

根因 3 — Model ID 和本地代理不匹配导致 fallback

openclaw.json
里配的 model ID 在 localhost proxy 上不存在,静默 fallback 到下一个。

# 查实际可用的模型
curl -s http://localhost:8317/v1/models -H "Authorization: Bearer your-api-key-1" \
  | python3 -c "import json,sys; [print(m['id']) for m in json.load(sys.stdin)['data']]" | sort

对比

openclaw.json
里的 model ID,确保完全匹配。


Problem 8 — Cron Jobs 走了 OpenRouter

症状

openclaw status
里 cron session 显示
model=openrouter/auto
,不应该走云端。

根因:session 里有

authProfileOverride: openrouter:*
被持久化(某次 auth 自动切换的残留)。

诊断

python3 -c "
import json
for agent in ['main', 'sii']:
    path = f'~/.openclaw/agents/{agent}/sessions/sessions.json'
    try:
        data = json.load(open(path))
        for k, v in data.items():
            if 'cron' in k and 'openrouter' in v.get('authProfileOverride',''):
                print(f'[{agent}] {k}: auth={v[\"authProfileOverride\"]} model={v.get(\"model\")}')
    except: pass
"

修复:清掉所有 cron session 里的 openrouter 覆盖:

python3 -c "
import json, glob
for path in glob.glob('~/.openclaw/agents/*/sessions/sessions.json'):
    data = json.load(open(path))
    changed = False
    for k, v in data.items():
        if 'cron' in k and 'openrouter' in v.get('authProfileOverride',''):
            for field in ['model','modelProvider','authProfileOverride','authProfileOverrideSource','authProfileOverrideCompactionCount']:
                v.pop(field, None)
            changed = True
            print(f'Fixed: {k}')
    if changed:
        json.dump(data, open(path,'w'), ensure_ascii=False)
" && openclaw gateway restart

Problem 9 — Gateway 无反应 /
device signature invalid

症状

openclaw status
RPC probe: failed
,错误为
gateway closed (1008): device signature invalid
。飞书 bot 不回复,gateway 看起来在跑但 CLI 连不上。

根因:升级 openclaw 版本后,旧版 LaunchAgent(

com.clawdbot.gateway
,加载
openclaw-cn
)没有被清理,与新版
ai.openclaw.gateway
同时注册开机启动。旧进程先抢占 18789 端口,新版 CLI 因 device signature 格式不兼容被拒绝。

诊断

launchctl list | grep -i claw
# 如果同时出现 com.clawdbot.gateway 和 ai.openclaw.gateway,就是这个问题

openclaw gateway status 2>&1 | grep -E "RPC|device|pid"

修复

# 1. 备份并禁用旧版 LaunchAgent
cp ~/Library/LaunchAgents/com.clawdbot.gateway.plist \
   ~/Library/LaunchAgents/com.clawdbot.gateway.plist.bak
launchctl bootout gui/$UID/com.clawdbot.gateway
mv ~/Library/LaunchAgents/com.clawdbot.gateway.plist \
   ~/Library/LaunchAgents/com.clawdbot.gateway.plist.disabled

# 2. 重启新版 gateway
openclaw gateway restart

# 验证
openclaw gateway status 2>&1 | grep "RPC probe"
# 期望输出:RPC probe: ok

说明

.plist.disabled
文件 launchd 开机不会加载(只识别
.plist
后缀),相当于禁用但保留备份。


Problem 10 — 非 Owner 用户被拦截(OAuth 授权 / 工具调用)

症状:非应用 Owner 的飞书用户发消息后,授权入口或工具调用被拒绝,日志中出现

OwnerAccessDeniedError
Permission denied: Only the app owner is authorized

根因

assertOwnerAccessStrict
默认
ownerOnly=true
,所有非 Owner 用户全部拒绝。这是 openclaw-lark 上游的已知问题(issues #5、#12)。

修复方案 A — 配置关闭(推荐):在

openclaw.json
的飞书 channel 里加
uat.ownerOnly: false

# 用 python3 安全写入,避免手编 JSON 出错
python3 - <<'EOF'
import json

path = '/Users/tianyiliang/.openclaw/openclaw.json'  # 按实际路径调整
cfg = json.load(open(path))
ch = cfg.setdefault('channels', {}).setdefault('feishu', {})
uat = ch.setdefault('uat', {})
uat['enabled'] = True
uat['ownerOnly'] = False
json.dump(cfg, open(path, 'w'), indent=2, ensure_ascii=False)
print('Done. Run: openclaw gateway restart')
EOF

修复方案 B — 升级插件:如果你使用 workspace 源码版插件(

openclaw-lark
),该 patch 已包含在 PR #11 中:

  • src/core/config-schema.ts
    UATConfigSchema
    ownerOnly?: boolean
  • src/core/owner-policy.ts
    assertOwnerAccessStrict
    里检查
    ownerOnly
    flag

验证:重启 gateway 后,非 Owner 用户发消息,日志中不再出现

OwnerAccessDeniedError


Contributing

Found a new OpenClaw failure mode? Open a PR with:

  1. The symptom (exact error message or behavior)
  2. Root cause
  3. Diagnostic command
  4. Fix

Keep entries short and command-first. The doctor should be fast to consult.