{"record":{"id":"39aac5ba2eef3b65","repo":"larksuite/cli","slug":"plugin-q-s-filesystem-w","errorCode":null,"errorMessage":"plugin %q %s filesystem: %w","messagePattern":"plugin %q (.+?) filesystem: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skillpolicy/pluginfs.go","lineNumber":125,"sourceCode":"\t\t\tname:   name,\n\t\t\tisDir:  entry.IsDir(),\n\t\t\ttyp:    entry.Type(),\n\t\t}\n\t}\n\treturn safe, nil\n}\n\nfunc (p *pluginFS) recoverPath(op, path string, err *error) {\n\tif value := recover(); value != nil {\n\t\t*err = p.pathError(op, path, fmt.Errorf(\"panic: %v\", value))\n\t}\n}\n\nfunc (p *pluginFS) pathError(op, path string, cause error) error {\n\treturn &fs.PathError{\n\t\tOp:   op,\n\t\tPath: path,\n\t\tErr:  fmt.Errorf(\"plugin %q %s filesystem: %w\", p.owner, p.field, cause),\n\t}\n}\n\nvar errorsNilResult = fmt.Errorf(\"returned nil without an error\")\n\nfunc joinPath(parent, child string) string {\n\tif parent == \".\" {\n\t\treturn child\n\t}\n\treturn parent + \"/\" + child\n}\n\ntype pluginFile struct {\n\tfsys   *pluginFS\n\tpath   string\n\tsource fs.File\n}\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/skillpolicy/pluginfs.go#L107-L143","documentation":"pathError builds the canonical error for every failure of the plugin skill filesystem: an fs.PathError whose Err wraps 'plugin \"<owner>\" <field> filesystem: <cause>'. It attributes the failure to the plugin (owner) and which overlay field (Base/Overlay) it came from, wrapping the underlying cause so errors.Is on fs.ErrNotExist and friends still works.","triggerScenarios":"Any Open/Stat/ReadFile/ReadDir/Read/Close on the protected plugin FS fails: the plugin returns an error, returns nil with nil error (errorsNilResult), or panics (converted by the recovery boundaries).","commonSituations":"A plugin Overlay lacks a requested SKILL.md path; a plugin FS returns fs.ErrPermission; a plugin's directory entries contain a nil DirEntry; a host-integrator's Base FS is misconfigured.","solutions":["Unwrap the fs.PathError.Err and inspect the innermost cause with errors.Is/errors.As (fs.ErrNotExist, fs.ErrPermission, etc.).","Check the plugin named in the message and the Base/Overlay field to find which contributed filesystem is faulty.","For ErrNotExist causes, verify the requested path exists in the plugin's Overlay with the exact name.","For plugin authors: make the FS return valid errors and never (nil, nil)."],"exampleFix":"// handling the wrapped error as a caller\nfile, err := protected.Open(\"my-skill/SKILL.md\")\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrNotExist) {\n        // skill missing from plugin overlay\n    }\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func AsPluginPathError(err error) (*fs.PathError, bool) {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && strings.Contains(perr.Err.Error(), \"plugin \") {\n        return perr, true\n    }\n    return nil, false\n}","tryCatchPattern":"if err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) {\n        // perr.Op, perr.Path attribute the failing operation\n        // errors.Is(perr.Err, fs.ErrNotExist) still works through the wrap\n    }\n}","preventionTips":["Always inspect with errors.As to *fs.PathError, not string matching.","Use errors.Is for sentinel causes like fs.ErrNotExist/fs.ErrPermission.","Log the plugin owner and Base/Overlay field from the message when filing bugs.","Never swallow the error; the wrap preserves the original cause."],"tags":["go","plugin-filesystem","error-wrapping","patherror"],"backgroundTag":"plugin-fs-path-error","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}