Skip to content

fix(skills): align skills API with TUI command multi-directory discovery#2285

Merged
Hmbown merged 5 commits into
Hmbown:mainfrom
gaord:fix/skills-api-multi-dir
May 31, 2026
Merged

fix(skills): align skills API with TUI command multi-directory discovery#2285
Hmbown merged 5 commits into
Hmbown:mainfrom
gaord:fix/skills-api-multi-dir

Conversation

@gaord
Copy link
Copy Markdown
Contributor

@gaord gaord commented May 27, 2026

Summary

The /v1/skills API endpoint only searched a single directory (resolve_skills_dir), while the TUI's /skills command uses discover_for_workspace_and_dir() to search multiple directories (workspace-local + global). This means the API would miss skills installed in ~/.claude/skills, ~/.agents/skills, workspace .agents/skills, etc.

Changes

  • Use discover_for_workspace_and_dir() in both list_skills and set_skill_enabled handlers, matching the TUI /skills command behavior
  • Add is_bundled field to SkillEntry — identifies built-in skills via is_bundled_skill_name(), allowing clients to distinguish user skills from bundled skills
  • Add directories field to SkillsResponse — returns all searched directories so clients know where skills come from
  • Use skill.path instead of constructing path from skills_dir + name, since skills may come from different directories
  • Remove unused SkillRegistry import

Before

{
  "directory": "/Users/user/.codewhale/skills",
  "warnings": [],
  "skills": [
    { "name": "delegate", "description": "...", "path": "/Users/user/.codewhale/skills/delegate/SKILL.md", "enabled": true }
  ]
}

Only skills under ~/.codewhale/skills were discoverable.

After

{
  "directory": "/Users/user/.codewhale/skills",
  "directories": [
    "/Users/user/project/.agents/skills",
    "/Users/user/.agents/skills",
    "/Users/user/.claude/skills",
    "/Users/user/.codewhale/skills",
    "/Users/user/.deepseek/skills"
  ],
  "warnings": [],
  "skills": [
    { "name": "delegate", "description": "...", "path": "/Users/user/.codewhale/skills/delegate/SKILL.md", "enabled": true, "is_bundled": true },
    { "name": "my-custom-skill", "description": "...", "path": "/Users/user/.claude/skills/my-custom-skill/SKILL.md", "enabled": true, "is_bundled": false }
  ]
}

Skills from all configured directories are now discoverable, matching TUI behavior.

Test plan

  • Verify /v1/skills returns skills from ~/.claude/skills, ~/.agents/skills, etc.
  • Verify is_bundled is true for built-in skills (delegate, documents, pdf, etc.)
  • Verify directories lists all existing skill directories
  • Verify set_skill_enabled works for skills from any directory
  • Verify set_skill_enabled returns 404 for non-existent skills

Greptile Summary

This PR aligns the /v1/skills REST API with the TUI /skills command by switching from single-directory discovery to the full discover_for_workspace_and_dir multi-directory strategy. It also adds is_bundled per entry and a directories array to the response.

  • Multi-directory discovery: both list_skills and set_skill_enabled now use the new discover_skills_for_runtime_api helper, which mirrors the TUI's workspace + global search order and appends the configured skills_dir when it is outside the standard set.
  • is_bundled field: intended to distinguish first-party bundled skills from user skills; computed via skill_entry_is_bundled which checks name against BUNDLED_SKILLS and path against the resolved skills_dir — but see the inline comment for a correctness issue with that path.
  • directories field: reports every directory that was actually searched, built with the same append-if-missing logic used internally by discovery.

Confidence Score: 4/5

Safe to merge with one fix: is_bundled will be wrong for bundled skills in any workspace that has a local .agents/skills or skills directory.

The multi-directory discovery wiring is correct and well-tested. The one defect is in skill_entry_is_bundled: it compares skill paths against resolve_skills_dir(), which prefers workspace-local directories, but bundled skills are always installed to config.skills_dir(). The mismatch means the new is_bundled field will silently return false for every genuine bundled skill whenever a workspace-local skills directory is present.

crates/tui/src/runtime_api.rs — specifically the skill_entry_is_bundled call in list_skills and the skills_dir argument passed to it.

Important Files Changed

Filename Overview
crates/tui/src/runtime_api.rs Aligns /v1/skills API with multi-directory discovery; adds is_bundled and directories fields to response. is_bundled logic incorrectly uses resolve_skills_dir() (which may be workspace-local) instead of the global config.skills_dir() where bundled skills are actually installed, causing bundled skills to be misreported when a workspace-local skills directory is present.
crates/tui/src/skills/mod.rs Refactors discover_for_workspace_dirs_and_dir to extract a new pub(crate) discover_from_directories helper; a clean, minimal change with no issues.

Sequence Diagram

sequenceDiagram
    participant Client
    participant list_skills
    participant resolve_skills_dir
    participant skills_search_directories
    participant discover_from_directories
    participant skill_entry_is_bundled

    Client->>list_skills: GET /v1/skills
    list_skills->>resolve_skills_dir: config, workspace
    resolve_skills_dir-->>list_skills: skills_dir (workspace-local or config.skills_dir())
    list_skills->>skills_search_directories: workspace, skills_dir
    skills_search_directories->>skills_search_directories: skills_directories(workspace) + append skills_dir if new
    skills_search_directories-->>list_skills: directories[]
    list_skills->>discover_from_directories: directories.clone()
    discover_from_directories-->>list_skills: SkillRegistry (merged, first-match-wins)
    loop for each skill
        list_skills->>skill_entry_is_bundled: skill, skills_dir (should be config.skills_dir())
        skill_entry_is_bundled-->>list_skills: is_bundled bool
    end
    list_skills-->>Client: SkillsResponse
Loading

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (3): Last reviewed commit: "fix(runtime): identify bundled skill ent..." | Re-trigger Greptile

- Use discover_for_workspace_and_dir() instead of SkillRegistry::discover()
  to search all skill directories (workspace + global), matching TUI /skills
- Add is_bundled field to SkillEntry for built-in skill identification
- Add directories field to SkillsResponse showing all search paths
- Use skill.path instead of constructing path from skills_dir + name
- Update set_skill_enabled to use the same discovery logic
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the runtime API to support multiple skill directories and bundled skills. It adds a directories field and an is_bundled flag to the skills response, and updates skill discovery to search across the workspace. A review comment points out that the directories list returned in the API response might omit a custom skills_dir if it is not already part of the default workspace directories, and suggests appending it to ensure consistency with the actual search paths.

Comment thread crates/tui/src/runtime_api.rs Outdated
let registry =
crate::skills::discover_for_workspace_and_dir(&state.workspace, &skills_dir);
let skill_state = state.skill_state.lock().await;
let directories = crate::skills::skills_directories(&state.workspace);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The directories list returned in SkillsResponse is populated using crate::skills::skills_directories(&state.workspace). However, discover_for_workspace_and_dir also searches skills_dir (the primary configured/resolved skills directory) and appends it to the search paths if it is not already present.

If a user has configured a custom skills_dir that is not in the default workspace/global candidate list, it will be searched for skills, but it will be missing from the directories field in the API response.

To ensure the returned directories list accurately reflects all directories that were actually searched, we should append skills_dir to directories if it is a valid directory and not already present, matching the behavior of discover_for_workspace_and_dir.

    let mut directories = crate::skills::skills_directories(&state.workspace);
    if skills_dir.is_dir() && !directories.contains(&skills_dir) {
        directories.push(skills_dir.clone());
    }

Comment thread crates/tui/src/runtime_api.rs Outdated
Comment thread crates/tui/src/runtime_api.rs
@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 31, 2026

Thanks for aligning the skills API with the TUI multi-directory discovery path. I am keeping this open because the API response can still become inconsistent: discover_for_workspace_and_dir can search a custom configured skills_dir, but the returned directories list may omit that custom directory.

Please mirror the same append logic when building directories, and keep the 404 message useful by including the searched paths. After that, rerun the runtime API/skills tests and full lint; the core direction is good.

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 31, 2026

Thanks @gaord — I pushed a maintainer update that keeps your multi-directory skills API direction and closes the two review gaps.

What changed:

  • directories now mirrors the actual discovery set by appending a custom configured skills_dir when discovery searches it
  • the unknown-skill 404 includes the searched directories, so clients still get useful debugging context
  • added a focused regression for custom skills directory reporting
  • merged current main so the branch includes the latest transcript/provider changes

Local verification on the updated head:

  • cargo fmt --all -- --check
  • python3 scripts/check-provider-registry.py
  • cargo test -p codewhale-tui skills_search_directories_includes_custom_skills_dir -- --nocapture
  • cargo check -p codewhale-tui --all-features --locked

Really useful parity fix; this should be ready once the refreshed PR checks come back.

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 31, 2026

One more small maintainer pass before merge: I fixed the is_bundled response flag so a workspace/user override named like a bundled skill (for example delegate) is not mislabeled as first-party unless the discovered SKILL.md is the configured bundled copy. I also reused the same searched-directory list for discovery and 404 reporting so GET /v1/skills does not stat the directories twice.\n\nVerified locally on 39fc14b9:\n- cargo fmt --all -- --check\n- git diff --check\n- python3 scripts/check-provider-registry.py\n- cargo test -p codewhale-tui skill_entry_is_bundled_requires_configured_bundle_path -- --nocapture\n- cargo test -p codewhale-tui skills_search_directories_includes_custom_skills_dir -- --nocapture\n- cargo check -p codewhale-tui --all-features --locked\n\nThanks again @gaord. This is exactly the kind of API/TUI parity cleanup that helps the runtime surface feel coherent.

@Hmbown Hmbown merged commit d3904e6 into Hmbown:main May 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants