{"record":{"id":"fb68d58e24094203","repo":"charmbracelet/crush","slug":"read-skill-q-w","errorCode":null,"errorMessage":"read skill %q: %w","messagePattern":"read skill %q: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skills/catalog.go","lineNumber":101,"sourceCode":"\tresult := SkillReadResult{\n\t\tName:        skill.Name,\n\t\tDescription: skill.Description,\n\t\tSource:      source,\n\t\tBuiltin:     skill.Builtin,\n\t}\n\n\tif skill.Builtin {\n\t\tembeddedPath := \"builtin/\" + strings.TrimPrefix(skill.SkillFilePath, BuiltinPrefix)\n\t\tcontent, err := BuiltinFS().ReadFile(embeddedPath)\n\t\tif err != nil {\n\t\t\treturn nil, SkillReadResult{}, fmt.Errorf(\"read builtin skill %q: %w\", skillID, err)\n\t\t}\n\t\treturn content, result, nil\n\t}\n\n\tcontent, err := os.ReadFile(skill.SkillFilePath)\n\tif err != nil {\n\t\treturn nil, SkillReadResult{}, fmt.Errorf(\"read skill %q: %w\", skillID, err)\n\t}\n\treturn content, result, nil\n}\n\nfunc skillLabel(skillPaths []string, workingDir string, skill *Skill) (string, SourceType) {\n\tif skill.Builtin {\n\t\treturn string(SourceSystem) + \":\" + skill.Name, SourceSystem\n\t}\n\n\tcleanFile := filepath.Clean(skill.SkillFilePath)\n\tfor _, base := range skillPaths {\n\t\tcleanBase := filepath.Clean(base)\n\t\trel, err := filepath.Rel(cleanBase, cleanFile)\n\t\tif err != nil || escapesParent(rel) {\n\t\t\tcontinue\n\t\t}\n\n\t\tsource := SourceUser","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/skills/catalog.go#L83-L119","documentation":"ReadContent falls back to os.ReadFile(skill.SkillFilePath) for non-builtin skills and wraps any OS-level read failure with the skill ID for context. This is the standard file-read error path: the SKILL.md path recorded during discovery no longer exists, is unreadable, or is a directory. The underlying *fs.PathError is preserved via %w.","triggerScenarios":"Calling skills.ReadContent for a user/project skill whose SkillFilePath points to a deleted, moved, renamed, or permission-denied file; skill discovered on a mount that was later unmounted; file removed between discovery and read (TOCTOU).","commonSituations":"Skill directory deleted or moved after Crush started; project checked out to a different path; file permissions changed (e.g. chmod 000, root-owned file); .local skill file cleaned by a build.","solutions":["Verify the file at skill.SkillFilePath exists and is readable (ls -l / cat the path)","Re-run skill discovery so SkillFilePath is refreshed to the current location","Restore or fix permissions on the SKILL.md file","Remove or repair the broken skill directory so it stops being discovered"],"exampleFix":"// before\ncontent, err := skills.ReadContent(active, paths, dir, skillID)\n\n// after — check the skill file first\nif info, err := os.Stat(skillFilePath); err != nil || info.IsDir() {\n    return fmt.Errorf(\"skill file %s unavailable\", skillFilePath)\n}\ncontent, err := skills.ReadContent(active, paths, dir, skillID)","handlingStrategy":"try-catch","validationCode":"func readable(path string) error {\n    info, err := os.Stat(path)\n    if err != nil { return err }\n    if info.IsDir() { return fmt.Errorf(\"%s is a directory\", path) }\n    f, err := os.Open(path)\n    if err != nil { return err }\n    return f.Close()\n}","typeGuard":null,"tryCatchPattern":"content, _, err := skills.ReadContent(active, paths, dir, skillID)\nif err != nil {\n    var pathErr *fs.PathError\n    if errors.As(err, &pathErr) && errors.Is(pathErr, fs.ErrNotExist) {\n        return fmt.Errorf(\"skill file %s was moved or deleted; rediscover skills\", pathErr.Path)\n    }\n    return err\n}","preventionTips":["Re-run discovery after any file move/rename inside skill directories","Check file permissions when running under different users or containers","Avoid deleting skill directories while a session is running","Use errors.As with *fs.PathError to surface the exact failing path"],"tags":["skills","file-io","filesystem","not-found"],"backgroundTag":"file-not-found","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}