hashicorp/nomad · error
unexpected HTTP transport: %T
Error message
unexpected HTTP transport: %T
What it means
In api/api.go:409, cloneWithTimeout clones the client's http.Client so a per-request timeout can be applied; it asserts the Transport is a concrete *http.Transport. If a user configured Config.HttpClient with a custom round-tripper that is not *http.Transport (or wrapped it), the clone cannot preserve per-request timeouts and the client returns this error.
Source
Thrown at api/api.go:409
return config
}
// cloneWithTimeout returns a cloned httpClient with set timeout if positive;
// otherwise, returns the same client
func cloneWithTimeout(httpClient *http.Client, t time.Duration) (*http.Client, error) {
if httpClient == nil {
return nil, errors.New("nil HTTP client")
} else if httpClient.Transport == nil {
return nil, errors.New("nil HTTP client transport")
}
if t.Nanoseconds() < 0 {
return httpClient, nil
}
tr, ok := httpClient.Transport.(*http.Transport)
if !ok {
return nil, fmt.Errorf("unexpected HTTP transport: %T", httpClient.Transport)
}
// copy all public fields, to avoid copying transient state and locks
ntr := &http.Transport{
Proxy: tr.Proxy,
DialContext: tr.DialContext,
Dial: tr.Dial,
DialTLS: tr.DialTLS,
TLSClientConfig: tr.TLSClientConfig,
TLSHandshakeTimeout: tr.TLSHandshakeTimeout,
DisableKeepAlives: tr.DisableKeepAlives,
DisableCompression: tr.DisableCompression,
MaxIdleConns: tr.MaxIdleConns,
MaxIdleConnsPerHost: tr.MaxIdleConnsPerHost,
MaxConnsPerHost: tr.MaxConnsPerHost,
IdleConnTimeout: tr.IdleConnTimeout,
ResponseHeaderTimeout: tr.ResponseHeaderTimeout,
ExpectContinueTimeout: tr.ExpectContinueTimeout,View on GitHub (pinned to 482b49bf1a)
Solutions
- Use *http.Transport directly as HttpClient.Transport (start from a copy of http.DefaultTransport or cleanhttp.DefaultTransport)
- If you need instrumentation, wrap at the http.Client level only for direct requests, or apply timeouts via context instead of cloneWithTimeout
- Set an explicit non-negative timeout only when needed — negative/zero duration short-circuits the clone and avoids this path
- If wrapping is required, embed *http.Transport fields into a real *http.Transport copy rather than a custom RoundTripper type
Example fix
// before client.HttpClient.Transport = otelhttp.NewTransport(http.DefaultTransport) nodeClient, err := apiClient.GetNodeClientWithTimeout(nodeID, 5*time.Second) // panics into error // after tr := http.DefaultTransport.(*http.Transport).Clone() client.HttpClient.Transport = tr // use context for tracing instead nodeClient, err := apiClient.GetNodeClientWithTimeout(nodeID, 5*time.Second)
Defensive patterns
Strategy: validation
Validate before calling
if cfg.HttpClient != nil && cfg.HttpClient.Transport != nil {
if _, ok := cfg.HttpClient.Transport.(*http.Transport); !ok {
return errors.New("Nomad client requires *http.Transport")
}
} Type guard
func isHTTPTransport(c *api.Config) bool {
_, ok := c.HttpClient.Transport.(*http.Transport)
return ok
} Try / catch
nodeClient, err := client.GetNodeClientWithTimeout(nodeID, 5*time.Second)
if err != nil && strings.Contains(err.Error(), "unexpected HTTP transport") {
return fmt.Errorf("replace custom RoundTripper with *http.Transport: %w", err)
} Prevention
- Build HttpClient with cleanhttp.DefaultTransport() (a *http.Transport)
- Clone transports via tr.Clone() instead of wrapping in custom RoundTrippers
- Do per-request tracing/timeouts via context, not transport substitution
- Only set node clients (GetNodeClient*) on configs using the default transport shape
When it happens
Trigger: Calling GetNodeClient/GetNodeClientWithTimeout (or the anonymous wrapper) while Config.HttpClient.Transport is set to anything other than *http.Transport — e.g. an http2 transport, oteltracing round-tripper, or a struct embedding *http.Transport.
Common situations: Injecting an OpenTelemetry/instrumented RoundTripper into the Nomad client; using a custom transport for proxy auth or mTLS that wraps rather than extends *http.Transport; libraries like hashicorp/go-cleanhttp not used and a hand-rolled transport supplied.
Related errors
- unable to unmarshal response with status %d: %v
- invalid address '%s': %v
- unsupported scheme: %v
- invalid redirect location %q: %w
- Failed to parse X-Nomad-Index: %v
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/7f59e70ec8c1345b.
Report an issue: GitHub.