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
- Inspect the wrapped error (%w) — http: unexpected EOF means client disconnect, http: request body too large means body limits
- Ensure middleware that reads the request body resets it (req.Body = io.NopCloser(bytes.NewBuffer(...))) before the plugin proxy runs
- Raise server body-size limits if large uploads are legitimate
- 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
- Reset request bodies in middleware after reading (io.NopCloser over a buffer)
- Configure realistic body-size limits
- Check Content-Length vs actual bytes at the trust boundary
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
- failed to read plugin management request body
- failed to read body
- invalid_request
- plugin executor %s received nil HTTP request
- plugin executor %s http request panic: %v
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/df24671356e654ff.
Report an issue: GitHub.