router-for-me/CLIProxyAPI · error

pluginhost: save command-line auth %s: %w

Error message

pluginhost: save command-line auth %s: %w

What it means

This wraps a failure from tokenStore.Save while persisting one of the auths returned by a plugin command-line run; %s is the auth record's ID. The underlying error (via %w) explains the real cause — usually a filesystem problem in the auth directory or a serialization failure.

Source

Thrown at internal/pluginhost/command_line.go:387

	store := sdkAuth.GetTokenStore()
	if store == nil {
		return nil, fmt.Errorf("pluginhost: token store unavailable")
	}
	summary := h.hostConfigSummary()
	if summary.AuthDir != "" {
		if setter, okSetter := store.(interface{ SetBaseDir(string) }); okSetter {
			setter.SetBaseDir(summary.AuthDir)
		}
	}
	savedPaths := make([]string, 0, len(auths))
	for index, authData := range auths {
		record := h.AuthDataToCoreAuth(authData, "", "")
		if record == nil {
			return savedPaths, fmt.Errorf("pluginhost: command-line auth %d is invalid", index+1)
		}
		savedPath, errSave := store.Save(ctx, record)
		if errSave != nil {
			return savedPaths, fmt.Errorf("pluginhost: save command-line auth %s: %w", record.ID, errSave)
		}
		if strings.TrimSpace(savedPath) != "" {
			savedPaths = append(savedPaths, savedPath)
		}
	}
	return savedPaths, nil
}

func appendCommandLineSavedPaths(stdout []byte, savedPaths []string) []byte {
	if len(savedPaths) == 0 {
		return stdout
	}
	out := append([]byte(nil), stdout...)
	if len(out) > 0 && out[len(out)-1] != '\n' {
		out = append(out, '\n')
	}
	for _, savedPath := range savedPaths {
		if strings.TrimSpace(savedPath) == "" {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Read the wrapped error — it distinguishes permission problems from encoding problems.
  2. Verify the auth directory (config auth-dir, default auths/) exists and is writable by the process user: ls -ld and touch a test file.
  3. Fix ownership/permissions (chown/chmod) or mount a writable volume in containers.
  4. Re-run the command-line flow; note earlier auths may already be saved, so clean duplicates if the plugin cannot resume.

Example fix

# before: auth dir not writable
$ ls -ld /etc/cliproxy/auths
drwxr-xr-x 2 root root ... /etc/cliproxy/auths

# after
$ sudo chown -R cliproxy:cliproxy /etc/cliproxy/auths
$ ./cli-proxy-api <plugin command-line flow>
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the auth directory
info, err := os.Stat(authDir)
if err != nil || !info.IsDir() {
    return fmt.Errorf("auth dir %s missing", authDir)
}
if err := os.WriteFile(filepath.Join(authDir, ".write-check"), nil, 0o600); err != nil {
    return fmt.Errorf("auth dir %s not writable", authDir)
}

Type guard

func authDirWritable(dir string) bool {
    f, err := os.CreateTemp(dir, ".probe-*")
    if err != nil {
        return false
    }
    f.Close()
    os.Remove(f.Name())
    return true
}

Try / catch

paths, err := host.PersistCommandLineAuths(ctx, auths)
if err != nil && strings.Contains(err.Error(), "save command-line auth") {
    cause := errors.Unwrap(err) // real filesystem error
    if os.IsPermission(cause) {
        return fixPermissionsAndRetry(authDir, auths)
    }
    return err
}

Prevention

When it happens

Trigger: store.Save failing: auth directory not writable or missing, disk full, path permission issues, or the store rejecting the record. Entries before this one were already saved, so the batch is partially applied.

Common situations: Running the service with an auths/ directory owned by another user; containers with read-only volumes for auth data; auth dir relocated via config to a path that does not exist; SELinux/AppArmor denying writes.

Related errors


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