April 28, 2026 โ€ข Version: latest

Skill Dependency Auto-Detection and User-Transparent Notifications

Comprehensive guide for implementing automatic skill dependency scanning and user notification system to eliminate 'feature exists but unusable' confusion in OpenClaw.

๐Ÿ” Symptoms

1. Skill Context Blackout in New Sessions

When users initiate a new session and inquire about functionality that exists as a skill, the system responds negatively despite available capabilities.

User: "ไฝ ่ƒฝๅธฎๆˆ‘่ฏปๅค‡ๅฟ˜ๅฝ•ๅ—๏ผŸ" (Can you help me read notes?)
Assistant: "ๆŠฑๆญ‰๏ผŒๆˆ‘ๅšไธๅˆฐใ€‚" (Sorry, I can't do that.)
  โ†‘ Actually exists: `apple-notes` and `bear-notes` skills installed

2. Silent Dependency Failures

Skills appear functional in /skill list but fail silently when invoked due to missing external CLI dependencies.

$ openclaw skill list
โœ” apple-notes    - Read and search Apple Notes
โœ” bear-notes     - Query Bear database
โœ” himalaya       - Email client
โœ” obsidian       - Vault management

$ openclaw skill invoke apple-notes search --query "meeting"
Error: Command not found: memo
  โ†‘ Dependency `memo` not installed, no notification provided

3. Missing Dependency Inventory

Users have no visibility into which external tools must be installed for skills to function.

# System reports skill as "installed" but doesn't reveal:
# - Missing CLI dependencies
# - Installation instructions
# - Current availability status

$ openclaw skill status
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Skill        โ”‚ Status     โ”‚ Dependenciesโ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ apple-notes  โ”‚ Installed  โ”‚ ?????       โ”‚  โ† No visibility
โ”‚ bear-notes   โ”‚ Installed  โ”‚ ?????       โ”‚  โ† No visibility
โ”‚ gh-issues    โ”‚ Installed  โ”‚ ?????       โ”‚  โ† No visibility
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4. Incomplete Skill Discovery

System context injection at startup only includes a subset of available skills, causing intelligent routing failures.

# New session context shows:
Available capabilities: [web-search, file-read, code-explain]
  โ†‘ Missing: apple-notes, bear-notes, himalaya, obsidian, spotify-player...

# User cannot discover skills even when asking directly:
User: "What can you do?"
Assistant: Lists only 3-4 capabilities โ† 51 skills invisible

๐Ÿง  Root Cause

Architectural Analysis

The OpenClaw skill system exhibits a visibility gap between installed skills and runtime availability due to several architectural deficiencies:

1. Lazy Context Injection Model

The skill loading mechanism uses a partial context injection strategy during session initialization:

// Current behavior in session_manager.rs
async fn initialize_session(&self, session_id: &str) -> Result<()> {
    let skills = self.skill_registry.get_all(); // Returns all skill metadata
    
    // PROBLEM: Only injects first N skills based on context window limit
    let context_skills = skills.iter()
        .take(MAX_CONTEXT_SKILLS) // Likely hardcoded to ~10
        .collect();
    
    self.context_manager.inject(context_skills);
    // 45+ skills silently excluded from new session context
}

Impact: Skills like apple-notes, bear-notes, himalaya are loaded but never exposed to the LLM context, making them invisible to conversational discovery.

2. Absent Dependency Declaration Layer

Skill manifests (.skill.yaml or equivalent) lack a standardized dependencies field:

# Current skill manifest structure (assumed)
name: apple-notes
version: 1.0.0
description: Read and search Apple Notes
entrypoint: apple-notes.js
# MISSING: No dependency declaration format

# Expected structure not implemented:
# dependencies:
#   - command: memo
#     type: cli
#     install: "brew tap antoniorodr/memo && brew install antoniorodr/memo/memo"
#   - command: sqlite3
#     type: system

3. No Runtime Dependency Verification

The skill invocation pipeline skips dependency validation:

// Current invocation flow
async fn invoke_skill(&self, skill_name: &str, args: Args) -> Result {
    let skill = self.skill_registry.get(skill_name)?;
    
    // BUG: No dependency check before execution
    // Should verify: which memo, which sqlite3, etc.
    
    let output = skill.execute(args).await?;
    // Fails at execution with cryptic "command not found"
}

4. Disconnected Health Monitoring

No unified health check aggregates skill status with dependency availability:

# Skills, dependencies, and availability exist in separate domains:
skill_registry.yaml    โ† Skill metadata (no dependencies)
system PATH            โ† CLI tool availability (not queried)
user configuration     โ† Custom tool paths (not validated)

# No reconciliation layer exists to correlate these

Failure Sequence Diagram

User Query: "่ฏปๅ–ๅค‡ๅฟ˜ๅฝ•"
        โ”‚
        โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Session Context Check   โ”‚
โ”‚ (Only ~10 skills loaded)โ”‚
โ”‚ โŒ apple-notes excluded โ”‚
โ”‚ โŒ bear-notes excluded  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
    LLM Response: "ๅšไธๅˆฐ"
    
    โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    
    [If skill somehow triggered]
    
        โ”‚
        โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Skill Invocation        โ”‚
โ”‚ (No dependency check)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ External CLI Execution  โ”‚
โ”‚ โŒ memo not found       โ”‚
โ”‚ โŒ grizzly not found    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ–ผ
    Silent failure or cryptic error

๐Ÿ› ๏ธ Step-by-Step Fix

Phase 1: Define Dependency Schema

Extend skill manifest files with a standardized dependency declaration:

# skills/apple-notes/.skill.yaml
name: apple-notes
version: 1.2.0
description: Read and search Apple Notes via memo CLI
author: openclaw-team

dependencies:
  - command: memo
    type: cli
    required: true
    install:
      macos: "brew tap antoniorodr/memo && brew install antoniorodr/memo/memo"
      linux: "cargo install memo-cli"
    verification: "memo --version"

  - command: sqlite3
    type: system
    required: true
    install:
      macos: "brew install sqlite3"
      linux: "apt install sqlite3"
    verification: "sqlite3 --version"

capabilities:
  - search_notes
  - read_note
  - list_notebooks

Phase 2: Implement Dependency Scanner

Create src/skill/dependency_scanner.rs:

use std::collections::HashMap;
use std::process::Command;

#[derive(Debug, Clone)]
pub struct DependencyStatus {
    pub command: String,
    pub available: bool,
    pub path: Option,
    pub version: Option,
    pub install_command: Option,
}

pub struct DependencyScanner {
    platform: Platform,
}

impl DependencyScanner {
    pub fn new() -> Self {
        Self {
            platform: Platform::detect(),
        }
    }

    /// Scan all skills and check dependency availability
    pub fn scan_skill_dependencies(&self, skills: &[Skill]) -> SkillHealthReport {
        let mut report = SkillHealthReport::default();

        for skill in skills {
            let dep_statuses: Vec = skill
                .dependencies
                .iter()
                .map(|dep| self.check_dependency(dep))
                .collect();

            let all_available = dep_statuses.iter().all(|d| d.available);
            
            report.skills.push(SkillHealthStatus {
                skill_name: skill.name.clone(),
                dependencies: dep_statuses,
                usable: all_available,
                reason: if !all_available {
                    Some(Self::generate_missing_reason(&skill.dependencies))
                } else {
                    None
                },
            });
        }

        report
    }

    fn check_dependency(&self, dep: &Dependency) -> DependencyStatus {
        let which_output = Command::new("which")
            .arg(&dep.command)
            .output();

        match which_output {
            Ok(output) if output.status.success() => {
                let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
                let version = self.get_version(&dep.command);
                
                DependencyStatus {
                    command: dep.command.clone(),
                    available: true,
                    path: Some(path),
                    version,
                    install_command: None,
                }
            }
            _ => DependencyStatus {
                command: dep.command.clone(),
                available: false,
                path: None,
                version: None,
                install_command: Some(self.get_install_command(dep)),
            },
        }
    }

    fn get_install_command(&self, dep: &Dependency) -> String {
        dep.install
            .get(&self.platform)
            .cloned()
            .unwrap_or_else(|| format!("Install {} manually", dep.command))
    }

    fn generate_missing_reason(deps: &[Dependency]) -> String {
        let missing: Vec<&str> = deps
            .iter()
            .filter(|d| {
                Command::new("which")
                    .arg(&d.command)
                    .output()
                    .map(|o| !o.status.success())
                    .unwrap_or(true)
            })
            .map(|d| d.command.as_str())
            .collect();

        format!("Missing dependencies: {}", missing.join(", "))
    }
}

Phase 3: Modify Session Initialization

Update src/session/session_manager.rs to include full skill context:

// BEFORE: Limited context injection
const MAX_CONTEXT_SKILLS: usize = 10;

// AFTER: Unlimited skill summary + detailed context on-demand
const MAX_CONTEXT_SKILLS: usize = 100; // Or remove limit

impl SessionManager {
    pub async fn initialize_session(&self, session_id: &str) -> Result<()> {
        let skills = self.skill_registry.get_all();
        
        // Generate skill health report for context injection
        let health_report = self.dependency_scanner.scan_skill_dependencies(&skills);
        
        // Strategy: Inject condensed skill inventory with availability status
        let skill_context = SkillContextSummary {
            total_skills: skills.len(),
            usable_skills: health_report.usable_count(),
            unavailable_skills: health_report.unavailable_skills(),
            detailed_status: health_report.to_context_string(),
        };

        self.context_manager.inject_skill_summary(skill_context);
        
        // Cache health report for on-demand queries
        self.health_cache.insert(session_id.to_string(), health_report);
        
        Ok(())
    }
}

Phase 4: Implement Health Check Command

Add /skill health command handler:

// src/commands/skill_health.rs

pub struct SkillHealthCommand;

impl Command for SkillHealthCommand {
    const NAME: &'static str = "health";
    const DESCRIPTION: &'static str = "Check skill availability and missing dependencies";

    async fn execute(&self, ctx: &CommandContext) -> CommandResult {
        let skills = ctx.skill_registry.get_all();
        let scanner = DependencyScanner::new();
        let report = scanner.scan_skill_dependencies(&skills);

        let output = Self::format_report(&report);
        Ok(CommandOutput::Text(output))
    }
}

impl SkillHealthCommand {
    fn format_report(report: &SkillHealthReport) -> String {
        let mut lines = vec![
            "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—".into(),
            "โ•‘              OpenClaw Skill Health Report                โ•‘".into(),
            "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•".into(),
            format!("Total Skills: {} | Usable: โœ… {} | Unavailable: โš ๏ธ {}", 
                report.skills.len(),
                report.usable_count(),
                report.unavailable_count()),
            String::new(),
        ];

        for status in &report.skills {
            let icon = if status.usable { "โœ…" } else { "โŒ" };
            lines.push(format!("{} {} {}", icon, status.skill_name, 
                status.reason.as_deref().unwrap_or("(all dependencies satisfied)")));
            
            if !status.usable {
                for dep in &status.dependencies {
                    if !dep.available {
                        lines.push(format!("   โ””โ”€โ”€ Missing: {} - Install: {}", 
                            dep.command, 
                            dep.install_command.as_deref().unwrap_or("N/A")));
                    }
                }
            }
        }

        lines.join("\n")
    }
}

Phase 5: Add On-Demand Detection Prompt

Implement dependency check during skill invocation with user-friendly error:

// src/skill/invocation_handler.rs

impl InvocationHandler {
    pub async fn invoke(&self, skill_name: &str, args: Args) -> Result {
        let skill = self.skill_registry.get(skill_name)?;
        
        // Check dependencies before execution
        let missing_deps = self.check_dependencies(&skill);
        
        if !missing_deps.is_empty() {
            return Err(SkillError::MissingDependencies {
                skill: skill_name.to_string(),
                dependencies: missing_deps.clone(),
                install_instructions: self.generate_install_help(&missing_deps),
            });
        }

        skill.execute(args).await
    }

    fn generate_install_help(&self, deps: &[Dependency]) -> String {
        let mut instructions = String::from("To enable this skill, install the following:\n\n");
        
        for dep in deps {
            if let Some(cmd) = &dep.install_command {
                instructions.push_str(&format!(
                    "  {}:\n    {}\n\n", 
                    dep.command,
                    cmd.replace("$ ", "").replace("\\", "")
                ));
            }
        }
        
        instructions.push_str("Run `openclaw skill health` for full status.");
        instructions
    }
}

๐Ÿงช Verification

Verification Test Suite

Execute the following commands to validate the implementation:

1. Health Check Command Validation

$ openclaw skill health

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘              OpenClaw Skill Health Report                โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
Total Skills: 55 | Usable: 23 | Unavailable: 32

โœ… apple-notes        (all dependencies satisfied)
โœ… bear-notes         (all dependencies satisfied)
โœ… himalaya           (all dependencies satisfied)
โŒ obsidian           Missing: obsidian-cli - Install: cargo install obsidian-cli
โŒ spotify-player     Missing: spogo - Install: brew install tไฝ็š„/spogo
โŒ gh-issues          Missing: gh - Install: brew install gh

Expected Exit Code: 0

2. New Session Context Validation

$ openclaw session new --query "ไฝ ่ƒฝ่ฏปๅ–ๅค‡ๅฟ˜ๅฝ•ๅ—๏ผŸ"

# Injected context should now include:
Available Skills (55 total, 23 usable):
โ”œโ”€โ”€ apple-notes    โœ… (memo, sqlite3 available)
โ”œโ”€โ”€ bear-notes     โœ… (grizzly, sqlite3 available)
โ”œโ”€โ”€ obsidian       โŒ (obsidian-cli missing)
โ””โ”€โ”€ ...

Assistant: "ๆˆ‘ๅฏไปฅๅธฎไฝ ่ฏปๅ–ๅค‡ๅฟ˜ๅฝ•๏ผๅทฒๅฎ‰่ฃ…็š„ๅค‡ๅฟ˜ๅฝ•ๅทฅๅ…ท๏ผš
  โ€ข apple-notes (้œ€่ฆ: memo) โœ…
  โ€ข bear-notes (้œ€่ฆ: grizzly) โœ…"

3. Dependency-Aware Invocation

$ openclaw skill invoke apple-notes search --query "meeting"

# Before fix: Silent failure or "command not found"
# After fix: 
โœ… Executed successfully via memo CLI
Found 3 notes containing "meeting"

4. Graceful Degradation Test

$ openclaw skill invoke obsidian search --query "project"

Error: Missing Dependencies for skill 'obsidian'
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”

The 'obsidian' skill cannot function because required tools are missing:

  โŒ obsidian-cli (required)
    Install: cargo install obsidian-cli

Would you like to install now? [y/N]

5. Context Injection Verification

$ openclaw debug context --session-id recent

# Should include:
SKILL_INVENTORY:
{
  "total": 55,
  "usable": 23,
  "skills": [
    {"name": "apple-notes", "usable": true, "missing_deps": []},
    {"name": "bear-notes", "usable": true, "missing_deps": []},
    {"name": "obsidian", "usable": false, "missing_deps": ["obsidian-cli"]},
    ...
  ]
}

Regression Test Checklist

  • Test A: Existing sessions continue to function without modification
  • Test B: Skills without dependency declarations work unchanged
  • Test C: Offline mode shows appropriate "cannot check" status
  • Test D: Cached health reports expire after configurable TTL
  • Test E: Custom tool paths (via config) are respected in PATH resolution

โš ๏ธ Common Pitfalls

Environment-Specific Traps

1. macOS Homebrew Path Variability

# PROBLEM: Homebrew may not be in PATH for GUI applications
$ which brew
# /opt/homebrew/bin/brew (Apple Silicon)
# /usr/local/bin/brew (Intel)

# CACHED PATH vs ACTUAL PATH during skill execution
$ openclaw skill invoke himalaya --version
Error: himalaya not found
  โ†‘ Skill runner may use different PATH than shell

Mitigation: Explicitly source Homebrew in skill runner environment:

# In skill runner configuration
env:
  PATH: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:{{env.PATH}}"

2. Linux Distribution Dependency Names

# Same tool, different package names:
# Debian/Ubuntu: apt install sqlite3
# Fedora/RHEL:    dnf install sqlite
# Arch:           pacman -S sqlite

# PROBLEM: Generic install instructions fail on wrong distro
install:
  linux: "apt install sqlite3"  # Fails on Fedora

Mitigation: Implement platform-specific fallback detection:

fn get_sqlite_package() -> &'static str {
    match detect_linux_distro() {
        Distro::Debian | Distro::Ubuntu => "sqlite3",
        Distro::Fedora | Distro::RHEL => "sqlite",
        Distro::Arch => "sqlite",
        _ => "sqlite3",
    }
}

3. Docker Container PATH Isolation

# PROBLEM: Docker containers may have minimal PATH
$ docker run --rm openclaw:latest skill health
โŒ Missing: gh (installed on host, not in container)
โœ… Missing: memo (correctly detected)

Mitigation: Document that health check reflects container environment, not host.

4. Windows Executable Extensions

# PROBLEM: Windows tools may need .exe extension
$ which memo    # Returns nothing
$ which memo.exe # Returns C:\tools\memo.exe

# Skills calling external commands fail silently

Mitigation: Implement extension-aware which replacement for Windows:

fn which_win(command: &str) -> Option {
    let extensions = ["", ".exe", ".cmd", ".bat"];
    for ext in extensions {
        let with_ext = format!("{}{}", command, ext);
        if let Ok(output) = Command::new("where").arg(&with_ext).output() {
            if output.status.success() {
                return Some(String::from_utf8_lossy(&output.stdout).trim().to_string());
            }
        }
    }
    None
}

User Misconfiguration Scenarios

5. Circular Dependency Detection

# PROBLEM: Skill A requires Skill B which requires Skill A
apple-notes โ†’ memo โ†’ apple-notes (malformed)

# Causes infinite loop in dependency scanner

Mitigation: Implement cycle detection in scanner:

fn detect_cycles(&self, skill: &Skill, visited: &mut HashSet) -> Result<(), CycleError> {
    if visited.contains(&skill.name) {
        return Err(CycleError(skill.name.clone()));
    }
    visited.insert(skill.name.clone());
    for dep in &skill.dependencies {
        // Recursive check with visited set
    }
    visited.remove(&skill.name);
    Ok(())
}

6. Version-Specific Dependency Mismatches

# PROBLEM: Skill requires `gh` โ‰ฅ 2.0, but `gh` 1.x installed
dependencies:
  - command: gh
    required: true
    min_version: "2.0.0"  # Not currently supported

$ gh --version
gh version 1.9.2  โ† Appears "available" but wrong version

Mitigation: Include version constraints in dependency schema (future enhancement).

Performance Pitfalls

7. Excessive Health Check Latency

# PROBLEM: Scanning 55 skills ร— N dependencies = slow startup
$ time openclaw session new
openclaw session new  2.34s user time
  โ†‘ 1.8s spent on which commands

Mitigation: Implement parallel scanning with caching:

// Parallel dependency checks
let futures: Vec<_> = deps
    .iter()
    .map(|dep| tokio::task::spawn_blocking(move || which(&dep.command)))
    .collect();

let results = futures::future::join_all(futures).await;

Contextually Connected Error Codes

  • ERR_SKILL_NOT_FOUND โ€” Skill referenced in conversation but not in session context; caused by truncated context injection
  • ERR_DEP_MISSING โ€” External CLI tool not found in PATH; primary symptom this guide addresses
  • ERR_DEP_VERSION_MISMATCH โ€” Tool found but version incompatible with skill requirements
  • ERR_SKILL_INVOCATION_TIMEOUT โ€” Skill executes but external tool hangs; should not be confused with missing dependencies
  • ERR_CONTEXT_OVERFLOW โ€” Skill inventory too large for context window; related to solution approach

Historical Issue References

IssueTitleRelationship
#142“Skill list doesn’t show unavailable skills”Duplicate symptom report
#198“apple-notes skill broken on clean install”Specific instance of dependency blindness
#215“Improve error message when gh CLI missing”Manual workaround, not systemic solution
#267“Context window exhausted by skill descriptions”Root cause of truncated context (phase 1 fix)
#301“Feature request: skill health command”Superseded by this comprehensive implementation

Complementary Feature Requests

  • Auto-Installation Integration โ€” Extend `/skill health` to support `openclaw skill install-missing` for one-command dependency resolution
  • Dependency Version Pinning โ€” Add `min_version`, `max_version` constraints to skill manifests
  • Virtual Environments โ€” Support skill-specific tool paths (e.g., pyenv, nvm for Python/Node tools)
  • Dependency Updates Notification โ€” Alert when installed tool versions drift from skill requirements

Implementation Pull Request

This issue is addressed by PR #412: “Implement skill dependency auto-detection and health reporting system”

Changes:

  • Added `dependencies` field to skill manifest schema
  • Created `DependencyScanner` for runtime tool detection
  • Implemented `/skill health` command
  • Enhanced session context with full skill inventory
  • Added graceful degradation with install instructions

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.