diff --git a/internal/plugin/releases.go b/internal/plugin/releases.go index cf7d1f8..88f325a 100644 --- a/internal/plugin/releases.go +++ b/internal/plugin/releases.go @@ -241,6 +241,11 @@ func (c *client) uploadReleaseAsset(ctx context.Context, cfg Config, release *Re } defer func() { _ = file.Close() }() + stat, err := file.Stat() + if err != nil { + return fmt.Errorf("stat asset: %w", err) + } + contentType := mime.TypeByExtension(filepath.Ext(assetPath)) if contentType == "" { contentType = "application/octet-stream" @@ -250,6 +255,10 @@ func (c *client) uploadReleaseAsset(ctx context.Context, cfg Config, release *Re if err != nil { return fmt.Errorf("build upload request: %w", err) } + // GitHub's asset-upload endpoint requires an explicit Content-Length and + // rejects chunked transfer encoding. http.NewRequest does not infer the + // length for an *os.File body, so it must be set manually. + req.ContentLength = stat.Size() setGitHubHeaders(req, cfg.Token, contentType, baseURL) resp, err := c.httpClient.Do(req) diff --git a/internal/plugin/releases_test.go b/internal/plugin/releases_test.go index 4f06a01..06bf941 100644 --- a/internal/plugin/releases_test.go +++ b/internal/plugin/releases_test.go @@ -327,10 +327,19 @@ func TestUploadReleaseAssetsExpandsGlobs(t *testing.T) { if got := r.URL.Query().Get("name"); got == "" { t.Fatal("missing asset name query") } + // GitHub's real asset-upload endpoint requires an explicit + // Content-Length and rejects chunked transfer encoding, so guard + // against regressing to an *os.File body without a set length. + if r.ContentLength < 0 { + t.Fatalf("ContentLength = %d, want a non-negative explicit length (chunked transfer encoding not allowed)", r.ContentLength) + } body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("ReadAll() error = %v", err) } + if int64(len(body)) != r.ContentLength { + t.Fatalf("body length = %d, Content-Length header = %d", len(body), r.ContentLength) + } uploaded <- r.URL.Query().Get("name") + ":" + string(body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"state":"uploaded"}`))