GoogleContainerTools/skaffold · error

http %d, error %q

Error message

http %d, error %q

What it means

Download requires an HTTP 200 response; any other status code produces this error with the numeric code and the status text (e.g. 'http 404, error "404 Not Found"'). It is a guard against treating error pages or redirects-to-error bodies as valid payloads.

Source

Thrown at pkg/skaffold/util/http.go:40

	"net/http"

	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/version"
)

func Download(url string) ([]byte, error) {
	client := http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("creating http request: %w", err)
	}
	req.Header.Set("User-Agent", version.UserAgentWithClient())
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http %d, error %q", resp.StatusCode, resp.Status)
	}
	return io.ReadAll(resp.Body)
}

View on GitHub (pinned to a1189de023)

Solutions

  1. Check the status code in the message: 404 means wrong/removed URL; 403 means access denied; 429 means back off and retry.
  2. Verify the URL is still valid by curling it and comparing status.
  3. For GCS, confirm the object exists and the bucket is publicly readable (or use authenticated access).
  4. Add retry with backoff for transient 429/5xx before treating it as fatal.
  5. If a proxy interferes, allowlist the host or fix proxy credentials.

Example fix

// before: one-shot download, fails hard on 429
b, err := util.Download(remoteCfgURL)
// after: retry transient statuses
var b []byte
for i := 0; i < 3; i++ {
    b, err = util.Download(remoteCfgURL)
    if err == nil || !strings.Contains(fmt.Sprint(err), "http 4") || !strings.Contains(fmt.Sprint(err), "429") {
        break
    }
    time.Sleep(time.Duration(1<<i) * time.Second)
}
Defensive patterns

Strategy: retry

Validate before calling

// can't pre-validate server status, but can pre-check reachability
resp, err := http.Head(url)
if err == nil && resp.StatusCode >= 400 {
    return fmt.Errorf("URL returns %d before download", resp.StatusCode)
}

Type guard

func isHTTPStatusError(err error) bool { return strings.Contains(err.Error(), "http ") && strings.Contains(err.Error(), ", error ") }

func statusCodeFrom(err error) int {
    var code int
    fmt.Sscanf(err.Error(), "http %d", &code)
    return code
}

Try / catch

b, err := util.Download(url)
if err != nil {
    if code := statusCodeFrom(err); code == 429 || code >= 500 {
        // retry with exponential backoff
    } else if code == 404 {
        return fmt.Errorf("resource not found at %s — check the URL", url)
    }
    return err
}

Prevention

When it happens

Trigger: client.Do succeeds but resp.StatusCode != 200: 404 (file removed/moved on GCS), 403 (permissions/blocked by firewall), 429 (rate limited), 5xx (server error).

Common situations: Fetching a remote skaffold config or latest-version file whose path changed; GCS bucket made private; corporate proxies returning 403; rate limiting after many checks; typos in remote config URLs in skaffold.yaml.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/6500171c74f35666. Report an issue: GitHub.