router-for-me/CLIProxyAPI · error
create plugin directory: %w
Error message
create plugin directory: %w
What it means
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.
Source
Thrown at internal/pluginstore/install.go:522
}
return pluginFileInfo{ID: id, Path: filePath, Version: version}, true
}
func pluginExtension(goos string) string {
switch strings.ToLower(strings.TrimSpace(goos)) {
case "darwin", "mac", "macos", "osx":
return ".dylib"
case "windows":
return ".dll"
default:
return ".so"
}
}
func writeFileAtomic(targetPath string, data []byte, mode os.FileMode) error {
targetDir := filepath.Dir(targetPath)
if errMkdir := os.MkdirAll(targetDir, 0o755); errMkdir != nil {
return fmt.Errorf("create plugin directory: %w", errMkdir)
}
temp, errTemp := os.CreateTemp(targetDir, "."+filepath.Base(targetPath)+".tmp-*")
if errTemp != nil {
return fmt.Errorf("create temp plugin file: %w", errTemp)
}
tempPath := temp.Name()
removeTemp := true
closed := false
defer func() {
if !closed {
if errClose := temp.Close(); errClose != nil {
log.WithError(errClose).Debug("failed to close temp plugin file")
}
}
if removeTemp {
if errRemove := os.Remove(tempPath); errRemove != nil && !errors.Is(errRemove, os.ErrNotExist) {
log.WithError(errRemove).Debug("failed to remove temp plugin file")View on GitHub (pinned to 78f0c4079e)
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
Example fix
# before ls -la /opt/cliproxy/plugins # root:root drwxr-xr-x, server runs as 'app' # after chown -R app:app /opt/cliproxy/plugins
Defensive patterns
Strategy: try-catch
Validate before calling
func pluginsDirWritable(pluginsDir string) error {
probe := filepath.Join(pluginsDir, ".write-probe")
if err := os.MkdirAll(pluginsDir, 0o755); err != nil {
return fmt.Errorf("plugins dir not creatable: %w", err)
}
return os.WriteFile(probe, nil, 0o644)
} Try / catch
if _, err := store.InstallArchive(data, plugin, opts); err != nil {
if errors.Is(err, os.ErrPermission) { // wrapped through MkdirAll
log.Errorf("plugins dir %s not writable by uid=%d; fix ownership", opts.PluginsDir, os.Getuid())
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- stat target plugin: %w
- create temp plugin file: %w
- chmod temp plugin file: %w
- write temp plugin file: %w
- plugin_update_requires_restart
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/b7ced49038639e66.
Report an issue: GitHub.