hashicorp/packer · error

%s returned status %d

Error message

%s returned status %d

What it means

fetchStream treats any HTTP status >= 400 from the registry host as a failure and returns "%s returned status %d" after closing the body. Unlike 337 this is a successful HTTP round trip whose response is an error (404, 403, 500, etc.).

Source

Thrown at packer/plugin-getter/remote/getter.go:130

		return nil, err
	}
	return io.NopCloser(buf), nil
}

// fetchStream returns the response body of url for the caller to consume
// and close; fetch buffers it, for the small metadata files.
func (g *Getter) fetchStream(url string) (io.ReadCloser, error) {
	if g.HttpClient == nil {
		g.HttpClient = &http.Client{}
	}
	log.Printf("[DEBUG] remote-getter: getting %q", url)
	resp, err := g.HttpClient.Get(url)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch %s: %w", url, err)
	}
	if resp.StatusCode >= 400 {
		_ = resp.Body.Close()
		return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
	}
	return resp.Body, nil
}

func (g *Getter) fetch(url string) ([]byte, error) {
	body, err := g.fetchStream(url)
	if err != nil {
		return nil, err
	}
	defer func() { _ = body.Close() }()
	return io.ReadAll(body)
}

// pluginPath returns the URL path of the plugin below the host, e.g.
// "mirror/hashicorp/packer-plugin-docker" for source
// "plugins.example.com/mirror/hashicorp/docker".
func pluginPath(opts plugingetter.GetOptions) (dir string, pluginType string) {
	parts := opts.PluginRequirement.Identifier.Parts()

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Read the status code: 404 → verify the plugin address and version exist on the registry; 403/429 → wait or check rate limiting/auth; 5xx → retry later.
  2. Correct the required_plugins source address if it points at a nonexistent org/repo.
  3. Curl the failing URL directly to inspect the response body for more detail.
  4. Re-run the install after the server-side issue resolves.

Example fix

// before
myplugin = {
  source = "github.com/acme/packer-plugin-myplugin"
  version = "= 9.9.9"
}
// after (use a published version)
myplugin = {
  source = "github.com/acme/packer-plugin-myplugin"
  version = "= 1.0.0"
}
Defensive patterns

Strategy: fallback

Validate before calling

resp, err := http.Head(indexURL)
if err == nil && resp.StatusCode >= 400 {
    return fmt.Errorf("registry returned %d for %s; check address/version", resp.StatusCode, indexURL)
}

Try / catch

if _, err := g.Get("releases", opts); err != nil {
    var statusErrPresent = strings.Contains(err.Error(), "returned status")
    if statusErrPresent && strings.Contains(err.Error(), "404") {
        // verify plugin address/version exist; do not blind-retry
    } else if statusErrPresent {
        // 5xx/429: wait and retry with backoff
    }
}

Prevention

When it happens

Trigger: The index.json / releases / meta URL answers with 4xx/5xx — e.g. a 404 because the plugin or version directory does not exist, 403 from a blocked/rate-limited client, or 500 from the registry.

Common situations: Typo'd plugin address (github.com/org/name) resolving to a nonexistent registry path; requesting a version that was deleted or never published; hitting GitHub/registry rate limits (403/429); custom mirror misconfigured path layout.

Related errors


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/47bd12f45b118ede. Report an issue: GitHub.