{"record":{"id":"34f9230641cabcc2","repo":"air-verse/air","slug":"proxy-inject-failed-to-init-gzip-reader-w","errorCode":null,"errorMessage":"proxy inject: failed to init gzip reader: %w","messagePattern":"proxy inject: failed to init gzip reader: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"runner/proxy.go","lineNumber":125,"sourceCode":"}\n\nfunc (p *Proxy) Reload() {\n\tp.stream.Reload()\n}\n\nfunc (p *Proxy) BuildFailed(msg BuildFailedMsg) {\n\tp.stream.BuildFailed(msg)\n}\n\nfunc (p *Proxy) injectLiveReload(resp *http.Response) (string, bool, error) {\n\tvar reader io.Reader = resp.Body\n\tdecoded := false\n\n\tswitch detectContentEncoding(resp.Header) {\n\tcase encodingGzip:\n\t\tgzipReader, err := gzip.NewReader(resp.Body)\n\t\tif err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"proxy inject: failed to init gzip reader: %w\", err)\n\t\t}\n\t\tdefer gzipReader.Close()\n\t\treader = gzipReader\n\t\tdecoded = true\n\tcase encodingBrotli:\n\t\treader = brotli.NewReader(resp.Body)\n\t\tdecoded = true\n\t}\n\n\tbuf := new(bytes.Buffer)\n\tif _, err := buf.ReadFrom(reader); err != nil {\n\t\treturn \"\", decoded, fmt.Errorf(\"proxy inject: failed to read body from http response: %w\", err)\n\t}\n\tpage := buf.String()\n\n\t// the script will be injected before the end of the body tag. In case the tag is missing, the injection will be skipped with no error.\n\tbody := strings.LastIndex(page, \"</body>\")\n\tif body == -1 {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/air-verse/air/blob/71ea1dee05248122ed22b5870c7fb20a90d80ddd/runner/proxy.go#L107-L143","documentation":"When proxying, injectLiveReload detects a gzip Content-Encoding header and wraps resp.Body in gzip.NewReader to read the decompressed HTML before injecting the reload script. If the gzip stream is corrupt or empty, this error is returned.","triggerScenarios":"The upstream app responds with Content-Encoding: gzip but the body is not valid gzip data — e.g. the app sets the header manually but sends uncompressed bytes, or the stream is truncated.","commonSituations":"Backend frameworks double-encoding or lying about the encoding header; middleware setting Content-Encoding: gzip without actually compressing; proxying non-Golang servers with broken compression middleware.","solutions":["Fix the upstream app so it only sets Content-Encoding: gzip when it actually compresses the body","Disable compression in the upstream app/middleware so air receives plain responses","Check the wrapped %w error: gzip.ErrHeader / unexpected EOF indicate corrupt or truncated streams","Bypass other compression layers (CDN/ingress) between air's proxy and the app"],"exampleFix":"// before (upstream app)\nw.Header().Set(\"Content-Encoding\", \"gzip\")\nw.Write([]byte(html)) // not actually gzipped\n// after\nw.Write([]byte(html)) // let the framework compress or drop the header","handlingStrategy":"try-catch","validationCode":"// verify upstream doesn't lie about gzip\nresp, _ := http.Get(\"http://localhost:\" + appPort + \"/\")\nif resp.Header.Get(\"Content-Encoding\") == \"gzip\" {\n\tzr, err := gzip.NewReader(resp.Body)\n\tif err != nil {\n\t\tlog.Printf(\"upstream sends invalid gzip: %v\", err)\n\t}\n\t_ = zr\n}","typeGuard":null,"tryCatchPattern":"if err := startProxy(); err != nil && strings.Contains(err.Error(), \"failed to init gzip reader\") {\n\t// disable compression upstream, then retry\n}","preventionTips":["Only set Content-Encoding: gzip when actually compressing","Prefer framework-managed compression (middleware) over manual headers","Test the app directly (curl --compressed) before proxying through air","Keep dev-mode compression off for simpler proxying"],"tags":["proxy","gzip","http"],"backgroundTag":"invalid-gzip-stream","analyzedSha":"71ea1dee05248122ed22b5870c7fb20a90d80ddd","analyzedAt":"2026-08-31T20:27:54.676Z","schemaVersion":2},"datasetVersion":"2026-09-01T03:17:15.561Z"}