{"record":{"id":"333c4e85eff865fd","repo":"larksuite/cli","slug":"panic-v","errorCode":null,"errorMessage":"panic: %v","messagePattern":"panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skillpolicy/pluginfs.go","lineNumber":58,"sourceCode":"\tfile, err = p.source.Open(name)\n\tif err != nil {\n\t\treturn nil, p.pathError(\"open\", name, err)\n\t}\n\tif file == nil {\n\t\treturn nil, p.pathError(\"open\", name, errorsNilResult)\n\t}\n\tsafe := &pluginFile{fsys: p, path: name, source: file}\n\tif dir, ok := file.(fs.ReadDirFile); ok {\n\t\treturn &pluginReadDirFile{pluginFile: safe, dir: dir}, nil\n\t}\n\treturn safe, nil\n}\n\nfunc (p *pluginFS) Stat(name string) (info fs.FileInfo, err error) {\n\tdefer func() {\n\t\tif value := recover(); value != nil {\n\t\t\tinfo = nil\n\t\t\terr = p.pathError(\"stat\", name, fmt.Errorf(\"panic: %v\", value))\n\t\t}\n\t}()\n\tinfo, err = fs.Stat(p.source, name)\n\tif err != nil {\n\t\treturn nil, p.pathError(\"stat\", name, err)\n\t}\n\tif info == nil {\n\t\treturn nil, p.pathError(\"stat\", name, errorsNilResult)\n\t}\n\treturn snapshotFileInfo(info), nil\n}\n\nfunc (p *pluginFS) ReadFile(name string) (data []byte, err error) {\n\tdefer p.recoverPath(\"readfile\", name, &err)\n\tdata, err = fs.ReadFile(p.source, name)\n\tif err != nil {\n\t\treturn nil, p.pathError(\"readfile\", name, err)\n\t}","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/skillpolicy/pluginfs.go#L40-L76","documentation":"pluginFS.Stat wraps the underlying fs.Stat in a recover() so a panic inside the source filesystem (e.g. a plugin-provided fs.FS misbehaving) is converted into a normal error via p.pathError(\"stat\", name, ...) with message \"panic: %v\". The library throws/captures this to keep a panicking plugin filesystem from crashing the host process, surfacing it as a path-scoped error instead.","triggerScenarios":"The underlying fs.FS implementation passed to pluginFS panics during Stat — typically a nil-map or index-out-of-range bug, or nil receiver, inside a plugin-supplied or faulty fstab/embed implementation, for the requested path name.","commonSituations":"A third-party or hand-written fs.FS plugin with an internal bug; concurrency unsafety where two goroutines mutate shared state during Stat; a nil source FS wired into the plugin filesystem; corrupted lazy-initialized caches in a virtual FS.","solutions":["Inspect the panic value in the wrapped error to find the panicking fs.FS implementation and fix its bug","Guard plugin-provided FS state for concurrent access (mutex) or stop sharing it across goroutines","Ensure the source FS passed to the plugin filesystem is non-nil and properly initialized","Reproduce the Stat call against the raw source FS to capture the full panic stack"],"exampleFix":"// before (plugin FS with data race panics on Stat)\nfunc (f *myFS) Stat(name string) (fs.FileInfo, error) { return f.cache[name].info, nil } // nil map entry panic\n// after\nfunc (f *myFS) Stat(name string) (fs.FileInfo, error) {\n    f.mu.Lock(); defer f.mu.Unlock()\n    e, ok := f.cache[name]\n    if !ok { return nil, &fs.PathError{Op: \"stat\", Path: name, Err: fs.ErrNotExist} }\n    return e.info, nil\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// narrow the wrapped path error before handling\nfunc asPathError(err error) (*fs.PathError, bool) {\n    var pe *fs.PathError\n    if errors.As(err, &pe) { return pe, true }\n    return nil, false\n}","tryCatchPattern":"info, err := pfs.Stat(name)\nif err != nil {\n    if strings.HasPrefix(errors.Unwrap(err).Error(), \"panic:\") {\n        log.Printf(\"plugin FS panicked on stat %s: %v — failing over to direct source FS\", name, err)\n        return fallbackFS.Stat(name)\n    }\n    return info, err\n}","preventionTips":["Fuzz/panic-test custom fs.FS implementations before shipping them as plugins","Protect shared FS state with mutexes; never share unsynchronized caches across goroutines","Initialize the source FS eagerly so Stat cannot hit nil state","Keep the recover-and-wrap pattern in plugin boundary code and log the panic value with a stack trace where possible"],"tags":["skillpolicy","panic","pluginfs","filesystem"],"backgroundTag":"filesystem-panic-recovered","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"}