{"record":{"id":"022fce5daadb5754","repo":"caddyserver/caddy","slug":"http-d-reading-error-message-v","errorCode":null,"errorMessage":"HTTP %d: reading error message: %v","messagePattern":"HTTP (.+?): reading error message: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/commandfuncs.go","lineNumber":848,"sourceCode":"\t// expects; reuse is not of particular concern here\n\tclient := http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(_ context.Context, _, _ string) (net.Conn, error) {\n\t\t\t\treturn net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0))\n\t\t\t},\n\t\t},\n\t}\n\n\tresp, err := client.Do(req) //nolint:gosec // the only SSRF here would be self-sabotage I think\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"performing request: %v\", err)\n\t}\n\n\t// if it didn't work, let the user know\n\tif resp.StatusCode >= 400 {\n\t\trespBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"HTTP %d: reading error message: %v\", resp.StatusCode, err)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"caddy responded with error: HTTP %d: %s\", resp.StatusCode, respBody)\n\t}\n\n\treturn resp, nil\n}\n\n// DetermineAdminAPIAddress determines which admin API endpoint address should\n// be used based on the inputs. By priority: if `address` is specified, then\n// it is returned; if `config` is specified, then that config will be used for\n// finding the admin address; if `configFile` (and `configAdapter`) are specified,\n// then that config will be loaded to find the admin address; otherwise, the\n// default admin listen address will be returned.\nfunc DetermineAdminAPIAddress(address string, config []byte, configFile, configAdapter string) (string, error) {\n\t// Prefer the address if specified and non-empty\n\tif address != \"\" {\n\t\treturn address, nil\n\t}","sourceCodeStart":830,"sourceCodeEnd":866,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/cmd/commandfuncs.go#L830-L866","documentation":"The admin endpoint answered with HTTP >= 400, but while reading the (capped at 2 MiB) error response body the read itself failed. This is a secondary failure: the transport broke mid-response, so the CLI can report neither the status body nor succeed.","triggerScenarios":"The admin connection is severed after headers are sent — daemon killed mid-request, unix socket removed during the call, or a proxy in between closing the stream.","commonSituations":"Rare in practice; seen when the Caddy process is being torn down concurrently with a CLI call, or when something between the CLI and the daemon truncates responses.","solutions":["Retry the CLI command once the daemon is stable","Check for two processes fighting over the same admin endpoint","If reproducible, capture daemon logs around the request to find why the body stream was cut"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"reading error message\") { time.Sleep(time.Second); err = retryCmd() }","preventionTips":["Avoid issuing admin API calls while restarting the daemon","One writer at a time: serialize config-change scripts"],"tags":["cli","admin-api","transient","io"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}