{"record":{"id":"b7ced49038639e66","repo":"router-for-me/CLIProxyAPI","slug":"create-plugin-directory-w","errorCode":null,"errorMessage":"create plugin directory: %w","messagePattern":"create plugin directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":522,"sourceCode":"\t}\n\treturn pluginFileInfo{ID: id, Path: filePath, Version: version}, true\n}\n\nfunc pluginExtension(goos string) string {\n\tswitch strings.ToLower(strings.TrimSpace(goos)) {\n\tcase \"darwin\", \"mac\", \"macos\", \"osx\":\n\t\treturn \".dylib\"\n\tcase \"windows\":\n\t\treturn \".dll\"\n\tdefault:\n\t\treturn \".so\"\n\t}\n}\n\nfunc writeFileAtomic(targetPath string, data []byte, mode os.FileMode) error {\n\ttargetDir := filepath.Dir(targetPath)\n\tif errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil {\n\t\treturn fmt.Errorf(\"create plugin directory: %w\", errMkdir)\n\t}\n\n\ttemp, errTemp := os.CreateTemp(targetDir, \".\"+filepath.Base(targetPath)+\".tmp-*\")\n\tif errTemp != nil {\n\t\treturn fmt.Errorf(\"create temp plugin file: %w\", errTemp)\n\t}\n\ttempPath := temp.Name()\n\tremoveTemp := true\n\tclosed := false\n\tdefer func() {\n\t\tif !closed {\n\t\t\tif errClose := temp.Close(); errClose != nil {\n\t\t\t\tlog.WithError(errClose).Debug(\"failed to close temp plugin file\")\n\t\t\t}\n\t\t}\n\t\tif removeTemp {\n\t\t\tif errRemove := os.Remove(tempPath); errRemove != nil && !errors.Is(errRemove, os.ErrNotExist) {\n\t\t\t\tlog.WithError(errRemove).Debug(\"failed to remove temp plugin file\")","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L504-L540","documentation":"Wrapped error from os.MkdirAll inside writeFileAtomic (install.go:520-523). Before writing the plugin library atomically, the installer creates the target directory tree (<PluginsDir>/<GOOS>/<GOARCH>). Failure means the filesystem refused the mkdir: permission denied, a path component that is a file, read-only mount, or a nonexistent parent on some network filesystems.","triggerScenarios":"InstallArchive with options.PluginsDir pointing somewhere the process cannot write (e.g. /usr/local/lib owned by root while running as non-root), or PluginsDir set to a path where an existing regular file occupies a directory component.","commonSituations":"Running the server under a restricted user or container with a read-only volume mounted at the plugins dir; a stale file left where a directory should be; SELinux/AppArmor denial on the target path.","solutions":["Check permissions on every component of PluginsDir and grant write access to the running user (chown/chmod)","Confirm no regular file shadows a needed directory component; remove or relocate it","Point PluginsDir at a writable location (e.g. under the config dir or a writable volume) and retry"],"exampleFix":"# before\nls -la /opt/cliproxy/plugins  # root:root drwxr-xr-x, server runs as 'app'\n# after\nchown -R app:app /opt/cliproxy/plugins","handlingStrategy":"try-catch","validationCode":"func pluginsDirWritable(pluginsDir string) error {\n    probe := filepath.Join(pluginsDir, \".write-probe\")\n    if err := os.MkdirAll(pluginsDir, 0o755); err != nil {\n        return fmt.Errorf(\"plugins dir not creatable: %w\", err)\n    }\n    return os.WriteFile(probe, nil, 0o644)\n}","typeGuard":null,"tryCatchPattern":"if _, err := store.InstallArchive(data, plugin, opts); err != nil {\n    if errors.Is(err, os.ErrPermission) { // wrapped through MkdirAll\n        log.Errorf(\"plugins dir %s not writable by uid=%d; fix ownership\", opts.PluginsDir, os.Getuid())\n    }\n}","preventionTips":["Provision the plugins directory with correct ownership during deployment, not at install time","In containers, mount a writable volume at the configured plugins path","Run a writability probe at startup rather than failing mid-install"],"tags":["pluginstore","filesystem","permissions","install"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}