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
- 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.
- Correct the required_plugins source address if it points at a nonexistent org/repo.
- Curl the failing URL directly to inspect the response body for more detail.
- 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
- Validate plugin source addresses and pinned versions against the registry before install.
- Handle rate limits by authenticating or throttling bulk installs.
- Curl the exact URL from the error to read the server's response body.
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
- index.json contains no versions
- %q not implemented
- malformed filename expected %s{version}_{os}_{arch}
- wrong version: %s does not match expected %s
- wrong system, expected %s_%s got %s_%s
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/47bd12f45b118ede.
Report an issue: GitHub.