router-for-me/CLIProxyAPI · error

invalid_request

invalid_request

Error message

read install request: %w

What it means

Returned by pluginInstallRequestedVersion (plugin_store.go:433) with code invalid_request when reading the install request body with io.ReadAll fails. The install endpoint accepts the version either as a query parameter or a JSON body; if the body stream errors (client disconnect, malformed chunked transfer encoding, connection reset), the read fails and the request is aborted before parsing.

Source

Thrown at internal/api/handlers/management/plugin_store.go:433

	case pluginstore.InstallTypeGitHubRelease:
		releaseTag := strings.TrimSpace(result.ReleaseTag)
		if releaseTag == "" {
			return pluginstore.Manifest{}, fmt.Errorf("release tag is required")
		}
		return pluginstore.ManifestFromRelease(source, plugin, pluginstore.Release{TagName: releaseTag})
	default:
		return pluginstore.Manifest{}, fmt.Errorf("unsupported install type %q", result.InstallType)
	}
}

func pluginInstallRequestedVersion(c *gin.Context) (string, error) {
	requestedVersion := strings.TrimSpace(c.Query("version"))
	if c == nil || c.Request == nil || c.Request.Body == nil || c.Request.Body == http.NoBody {
		return requestedVersion, nil
	}
	body, errRead := io.ReadAll(c.Request.Body)
	if errRead != nil {
		return "", fmt.Errorf("read install request: %w", errRead)
	}
	if strings.TrimSpace(string(body)) == "" {
		return requestedVersion, nil
	}
	var req pluginInstallRequest
	if errDecode := json.Unmarshal(body, &req); errDecode != nil {
		return "", fmt.Errorf("decode install request: %w", errDecode)
	}
	bodyVersion := strings.TrimSpace(req.Version)
	if requestedVersion == "" {
		return bodyVersion, nil
	}
	if bodyVersion == "" || normalizePluginStoreRequestedVersion(bodyVersion) == normalizePluginStoreRequestedVersion(requestedVersion) {
		return requestedVersion, nil
	}
	return "", fmt.Errorf("version query %q does not match request body version %q", requestedVersion, bodyVersion)
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Retry the request — transient connection drops are the most common cause
  2. Send the version as a query parameter and an empty body: ?version=1.2.3 (body reading is skipped when the body is empty or http.NoBody)
  3. If behind a proxy, ensure Content-Length or proper chunked encoding is preserved
  4. Check the client for premature stream closure (e.g. timeout killing the upload)

Example fix

# before: body upload interrupted
# (curl killed mid-request -> "read install request: ...")

# after: pass version via query string, no body needed
curl -X POST -H "Authorization: Bearer $KEY" \
  'http://127.0.0.1:8000/management/plugin-store/install?plugin_id=foo&version=1.2.3'
Defensive patterns

Strategy: retry

Try / catch

resp, err := httpClient.Do(req)
if err != nil {
	// transient transport failure: rebuild and retry once
	time.Sleep(time.Second)
	resp, err = httpClient.Do(cloneRequest(req))
}
if err != nil {
	return err
}

Prevention

When it happens

Trigger: POST /management/plugin-store/install with a chunked/streaming body that is cut off mid-transfer; client dropping the connection while the server reads; proxy in front of the API mangling transfer encoding.

Common situations: Scripts piping request bodies that terminate early; flaky networks between an operator's machine and the management API; HTTP proxies re-encoding the body incorrectly.

Related errors


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