github/copilot-sdk · error

failed to save tarball

Error message

failed to save tarball: %w

What it means

downloadCLIBinary streams the release tarball from the network into a local file while simultaneously computing its SHA-256 via io.MultiWriter. This error wraps any failure from that io.Copy of resp.Body, meaning the download was interrupted or the write to the tarball file failed mid-stream. It is thrown to distinguish save-time I/O problems from earlier file-creation problems.

Solutions

  1. Retry the download; the error is usually transient (network or disk pressure).
  2. Check free disk space on the destination filesystem (tarballs plus extracted binaries can be hundreds of MB).
  3. Verify network/proxy stability (HTTP_PROXY, VPN, firewall) and re-run buildBundle.
  4. Check upstream release-host availability if failures persist across retries.

Example fix

// before: single-shot download with no retry
tarballPath, licensePath, err := downloadCLIBinary(...)

// after: retry transient save failures
var tarballPath, licensePath string
for attempt := 0; attempt < 3; attempt++ {
    tarballPath, licensePath, err = downloadCLIBinary(...)
    if err == nil || !strings.Contains(err.Error(), "failed to save tarball") {
        break
    }
    time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
}
Defensive patterns

Strategy: retry

Validate before calling

// preflight disk space before downloading
if st, err := os.Statfs(destDir); err == nil && st.Avail < 1<<30 {
    return fmt.Errorf("insufficient space in %s for tarball download", destDir)
}

Try / catch

for i := 0; i < 3; i++ {
    path, lic, err := downloadCLIBinary(...)
    if err == nil { break }
    if isIOCopyFailure(err) { time.Sleep(backoff(i)); continue }
    return err
}

Prevention

When it happens

Trigger: io.Copy(io.MultiWriter(tarballFile, hasher), resp.Body) returns a non-nil error while downloading the release tarball — network drop, connection reset, disk full while writing the file, or the HTTP body read failing.

Common situations: Flaky network or proxy dropping long downloads of large CLI binaries; disk quota/full /tmp; corporate firewall terminating TLS mid-transfer; transient GitHub release-asset download failure.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/f91c280ec4e26f91. Report an issue: GitHub.

Appendix: source

Thrown at go/cmd/bundler/main.go:1022

		return "", "", fmt.Errorf("failed to download: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", "", fmt.Errorf("failed to download: %s", resp.Status)
	}

	// Save tarball to temp file
	tarballPath := filepath.Join(destDir, assetName)
	tarballFile, err := os.Create(tarballPath)
	if err != nil {
		return "", "", fmt.Errorf("failed to create tarball file: %w", err)
	}

	hasher := sha256.New()
	if _, err := io.Copy(io.MultiWriter(tarballFile, hasher), resp.Body); err != nil {
		tarballFile.Close()
		return "", "", fmt.Errorf("failed to save tarball: %w", err)
	}
	if err := tarballFile.Close(); err != nil {
		return "", "", fmt.Errorf("failed to close tarball file: %w", err)
	}
	actualChecksum := fmt.Sprintf("%x", hasher.Sum(nil))
	if actualChecksum != expectedChecksum {
		return "", "", fmt.Errorf(
			"checksum mismatch for %s: expected %s, got %s",
			assetName,
			expectedChecksum,
			actualChecksum,
		)
	}

	// The SDK release package intentionally omits the legacy SEA binary. Preserve
	// embeddedcli.Path compatibility by installing the runtime wrapper under the
	// historical copilot[.exe] name; the normal client path uses the adjacent
	// wrapper/runtime.node pair directly.

View on GitHub (pinned to cd8cf15dc3)