Fix read_skill tool failing on symlinked skills

Nathan Sobo created

The read_skill tool validates that requested paths are within a skills
directory by canonicalizing the input path and checking it starts with
the skills root. However, when skills are symlinks (e.g. pointing to
~/.config/zed/superpowers/skills/), canonicalization resolves the symlink
to its target, which no longer starts with the non-canonicalized skills
root.

Fix by also canonicalizing the skills root paths before the starts_with
check, so symlink targets are correctly recognized as being within the
skills directory.

Also update skills_prompt.hbs to reference the read_skill tool instead
of read_file/list_directory, which are project-scoped and cannot access
the global skills directory.

Change summary

crates/agent/src/skills.rs                   |  9 +++++++--
crates/agent/src/templates/skills_prompt.hbs | 19 ++++---------------
2 files changed, 11 insertions(+), 17 deletions(-)

Detailed changes

crates/agent/src/skills.rs 🔗

@@ -311,13 +311,18 @@ pub fn is_skills_path_canonical(
     worktree_roots: &[PathBuf],
 ) -> Option<PathBuf> {
     let global_skills_root = global_skills_dir();
-    if canonical_input.starts_with(&global_skills_root) {
+    // Canonicalize the skills roots so that symlinks within the skills directory
+    // resolve to paths that still pass the `starts_with` check.
+    let canonical_global = std::fs::canonicalize(&global_skills_root).unwrap_or(global_skills_root);
+    if canonical_input.starts_with(&canonical_global) {
         return Some(canonical_input.to_path_buf());
     }
 
     for worktree_root in worktree_roots {
         let worktree_skills_path = worktree_root.join(".agents").join("skills");
-        if canonical_input.starts_with(&worktree_skills_path) {
+        let canonical_worktree =
+            std::fs::canonicalize(&worktree_skills_path).unwrap_or(worktree_skills_path);
+        if canonical_input.starts_with(&canonical_worktree) {
             return Some(canonical_input.to_path_buf());
         }
     }

crates/agent/src/templates/skills_prompt.hbs 🔗

@@ -11,18 +11,7 @@ Each skill contains:
 - `references/` - Additional documentation
 - `assets/` - Templates, data files, images
 
-To use a skill, you should use the read_file and list_directory tools to explore skill files, and only use bash for running executable scripts:
-
-**For reading and exploring (use internal tools):**
-- Read SKILL.md: `read_file` with path `~/.config/zed/skills/<skill-name>/SKILL.md`
-- List skill references: `list_directory` with path `~/.config/zed/skills/<skill-name>/references/`
-- List skill scripts: `list_directory` with path `~/.config/zed/skills/<skill-name>/scripts/`
-- Read references: `read_file` with path `~/.config/zed/skills/<skill-name>/references/doc.md`
-
-**For running scripts only (use bash):**
-- Run scripts: `bash ~/.config/zed/skills/<skill-name>/scripts/script.sh`
-
-Note: You typically want to run tools for the current project you're working on, which is why you should use your worktree root as the working directory.
+To use a skill, first read its SKILL.md file, then explore its resources as needed.
 
 | Skill | Description | Location |
 |-------|-------------|----------|
@@ -31,9 +20,9 @@ Note: You typically want to run tools for the current project you're working on,
 {{/each}}
 
 When a skill is relevant to the user's request:
-1. **Always start here:** Read SKILL.md to load the skill's instructions using `read_file` with path `~/.config/zed/skills/<skill-name>/SKILL.md`
+1. **Always start here:** Read SKILL.md to load the skill's instructions using `read_skill` with path `~/.config/zed/skills/<skill-name>/SKILL.md`
 2. **Then, based on the instructions in SKILL.md and the user's specific needs:**
-   - Use `list_directory` to explore available resources
-   - Use `read_file` to read reference files
+   - Use `read_skill` to explore available resources
+   - Use `read_skill` to read reference files
    - Use `bash` only to run executable scripts
 {{/if}}