{"record":{"id":"1c2484392342040a","repo":"larksuite/cli","slug":"returned-nil-without-an-error","errorCode":null,"errorMessage":"returned nil without an error","messagePattern":"returned nil without an error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skillpolicy/pluginfs.go","lineNumber":129,"sourceCode":"\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\nfunc (f *pluginFile) Stat() (info fs.FileInfo, err error) {\n\tdefer func() {\n\t\tif value := recover(); value != nil {\n\t\t\tinfo = nil","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/skillpolicy/pluginfs.go#L111-L147","documentation":"errorsNilResult ('returned nil without an error') is the sentinel for a plugin filesystem contract violation: the plugin's fs.FS/fs.File/fs.DirEntry/fs.FileInfo method returned a nil result with a nil error, which the fs interfaces forbid. The wrapper turns this into an fs.PathError so callers never receive a nil file/info that would panic later.","triggerScenarios":"pluginFS.Open gets (nil, nil) from the plugin (pluginfs.go:45); Stat gets a nil FileInfo (pluginfs.go:66); ReadDir yields a nil DirEntry (pluginfs.go:98); pluginFile.Stat or pluginDirEntry.Info get nil FileInfo with nil error.","commonSituations":"A hand-rolled plugin FS returns nil,nil on a not-found path instead of fs.ErrNotExist; a generated or mock FS in a plugin forgets to return an error; a plugin FileInfo wrapper returns nil Info for its dir entries.","solutions":["Fix the plugin FS to return a real error (e.g. &fs.PathError{Op:..., Err: fs.ErrNotExist}) instead of (nil, nil).","As a caller, read the fs.PathError — the plugin owner and op fields identify the offending method.","Add contract tests to the plugin: every method must return non-nil results or a non-nil error.","If using a mock/test FS in the plugin, implement it with fstest.MapFS to guarantee correct semantics."],"exampleFix":"// before (plugin FS, contract violation)\nfunc (f myFS) Open(name string) (fs.File, error) {\n    file, ok := f.files[name]\n    if !ok {\n        return nil, nil // forbidden: nil, nil\n    }\n    return file, nil\n}\n// after\nfunc (f myFS) Open(name string) (fs.File, error) {\n    file, ok := f.files[name]\n    if !ok {\n        return nil, &fs.PathError{Op: \"open\", Path: name, Err: fs.ErrNotExist}\n    }\n    return file, nil\n}","handlingStrategy":"validation","validationCode":"// before relying on a custom plugin FS, verify contract compliance\nif err := fstest.TestFS(pluginFSInstance, \"my-skill/SKILL.md\"); err != nil {\n    return fmt.Errorf(\"plugin FS violates fs.FS contract: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if strings.Contains(err.Error(), \"returned nil without an error\") {\n        return fmt.Errorf(\"plugin FS returned nil,nil — plugin bug: %w\", err)\n    }\n    return err\n}","preventionTips":["Implement plugin FS with fstest.MapFS when possible.","Never return (nil, nil) from any fs.FS/fs.File/fs.DirEntry/fs.FileInfo method.","Add fstest.TestFS checks to the plugin's test suite.","Review custom FileInfo wrappers for nil Info returns."],"tags":["go","plugin-filesystem","contract-violation","nil-result"],"backgroundTag":"plugin-fs-nil-result","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"}