router-for-me/CLIProxyAPI · error

read target plugin: %w

Error message

read target plugin: %w

What it means

Wrapped error from InstallArchive when an existing plugin file is detected at the target path (overwritten == true) but os.ReadFile on it fails. The existing bytes are needed for an idempotency check: if the installed library is byte-identical to the new one, the install is skipped. If the file cannot be read (permissions, IO error), the comparison is impossible and the install aborts instead of clobbering an unknown state.

Source

Thrown at internal/pluginstore/install.go:277

	libraryData, mode, errLibrary := readTargetLibrary(reader, id, version, options.GOOS)
	if errLibrary != nil {
		return InstallResult{}, errLibrary
	}

	targetPath, errTarget := installTargetPath(options, id, version)
	if errTarget != nil {
		return InstallResult{}, errTarget
	}
	overwritten := false
	if _, errStat := os.Stat(targetPath); errStat == nil {
		overwritten = true
	} else if !errors.Is(errStat, os.ErrNotExist) {
		return InstallResult{}, fmt.Errorf("stat target plugin: %w", errStat)
	}
	if overwritten {
		existingData, errReadExisting := os.ReadFile(targetPath)
		if errReadExisting != nil {
			return InstallResult{}, fmt.Errorf("read target plugin: %w", errReadExisting)
		}
		if bytes.Equal(existingData, libraryData) {
			return InstallResult{
				ID:          id,
				Version:     strings.TrimSpace(plugin.Version),
				Path:        targetPath,
				Overwritten: true,
				Skipped:     true,
			}, nil
		}
	}
	// Re-check immediately before replacing an existing file: the same version
	// may have been loaded while the archive was being downloaded and verified.
	if overwritten && options.BeforeWrite != nil {
		if errBeforeWrite := options.BeforeWrite(); errBeforeWrite != nil {
			return InstallResult{}, fmt.Errorf("prepare plugin write: %w", errBeforeWrite)
		}
	}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Fix ownership/permissions of the existing target file so the current process can read it (chown/chmod)
  2. Delete the stale target file if it is known to be garbage, so the install takes the fresh-create path
  3. For recurring problems, run installs as a single dedicated user that owns PluginsDir

Example fix

# before
-rw------- root root /opt/app/plugins/linux/amd64/myplugin-v1.0.0.so

# after
chown app-user:app-user /opt/app/plugins/linux/amd64/myplugin-v1.0.0.so && chmod 644 /opt/app/plugins/linux/amd64/myplugin-v1.0.0.so
# or remove it entirely: rm /opt/app/plugins/linux/amd64/myplugin-v1.0.0.so
Defensive patterns

Strategy: try-catch

Validate before calling

func targetReadable(targetPath string) bool {
    f, err := os.Open(targetPath)
    if err != nil {
        return false
    }
    defer func() { _ = f.Close() }()
    return true
}

Try / catch

if _, err := pluginstore.InstallArchive(data, plugin, options); err != nil {
    if strings.Contains(err.Error(), "read target plugin:") {
        if errors.Is(err, fs.ErrPermission) {
            // chown/chmod the existing target file, or remove it to force fresh install
        }
    }
}

Prevention

When it happens

Trigger: Target file exists but is unreadable by the process: mode 0000 or owned by another user; file locked by mandatory file locking on some platforms; I/O errors from a failing disk or an evaporated network mount.

Common situations: Earlier install ran as root leaving a root-owned 0600 file, later installs run unprivileged; NFS/FUSE mounts with permission mapping issues; security agents or antivirus holding the file.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/ff346bf5edaadfc6. Report an issue: GitHub.