router-for-me/CLIProxyAPI · error

read plugin http request body: %w

Error message

read plugin http request body: %w

What it means

The host failed while reading the inbound *http.Request body (via readAndRestoreRequestBody) before handing it to the plugin. The underlying io.ReadAll error is wrapped, and the original request body is restored so it can be retried or logged. The request never reached the plugin.

Source

Thrown at internal/pluginhost/adapters_executors.go:804

}

func (a *executorAdapter) HttpRequest(ctx context.Context, auth *coreauth.Auth, req *http.Request) (resp *http.Response, err error) {
	if a == nil || a.executor == nil || a.host.isPluginFused(a.pluginID) || !a.host.pluginIdentityCurrent(a.pluginID, a.path, a.version) {
		return nil, fmt.Errorf("plugin executor %s is unavailable", a.Identifier())
	}
	if req == nil {
		return nil, fmt.Errorf("plugin executor %s received nil HTTP request", a.Identifier())
	}
	defer func() {
		if recovered := recover(); recovered != nil {
			a.host.fusePlugin(a.pluginID, "Executor.HttpRequest", recovered)
			resp = nil
			err = fmt.Errorf("plugin executor %s http request panic: %v", a.Identifier(), recovered)
		}
	}()
	body, errReadAll := readAndRestoreRequestBody(req)
	if errReadAll != nil {
		return nil, fmt.Errorf("read plugin http request body: %w", errReadAll)
	}
	pluginResp, errHTTPRequest := a.executor.HttpRequest(ctx, pluginapi.ExecutorHTTPRequest{
		AuthID:       authID(auth),
		AuthProvider: authProvider(auth),
		Method:       req.Method,
		URL:          req.URL.String(),
		Headers:      cloneHeader(req.Header),
		Body:         bytes.Clone(body),
		StorageJSON:  storageJSONFromAuth(auth),
		Metadata:     cloneAnyMap(authMetadata(auth)),
		Attributes:   authAttributes(auth),
		HTTPClient:   a.host.newHTTPClient(auth, a.provider),
	})
	if errHTTPRequest != nil {
		return nil, errHTTPRequest
	}
	status := pluginResp.StatusCode
	if status == 0 {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Inspect the wrapped error (%w) — http: unexpected EOF means client disconnect, http: request body too large means body limits
  2. Ensure middleware that reads the request body resets it (req.Body = io.NopCloser(bytes.NewBuffer(...))) before the plugin proxy runs
  3. Raise server body-size limits if large uploads are legitimate
  4. Retry idempotent requests after transient network failures
Defensive patterns

Strategy: retry

Validate before calling

// Before proxying, ensure the body is re-readable and intact:
if req.Body != nil {
    body, readErr := io.ReadAll(req.Body)
    req.Body.Close()
    if readErr != nil { return readErr }
    req.Body = io.NopCloser(bytes.NewReader(body))
}

Try / catch

resp, err := adapter.HttpRequest(ctx, auth, req)
if err != nil {
    var netErr net.Error
    if errors.Is(err, context.Canceled) || errors.As(err, &netErr) {
        return nil, err // client gone or transient; safe to abort or retry once
    }
    return nil, err
}

Prevention

When it happens

Trigger: The request's Body reader returns an error mid-read — e.g. client disconnected mid-upload, a Content-Length mismatch, a gzip body that fails decompression, or a body already consumed and closed by earlier middleware.

Common situations: Client drops the connection during a large upload; middleware chain reading/closing the body before the plugin proxy; decompressing reverse proxy in front of the host.

Related errors


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