{"record":{"id":"7f59e70ec8c1345b","repo":"hashicorp/nomad","slug":"unexpected-http-transport-t","errorCode":null,"errorMessage":"unexpected HTTP transport: %T","messagePattern":"unexpected HTTP transport: %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/api.go","lineNumber":409,"sourceCode":"\treturn config\n}\n\n// cloneWithTimeout returns a cloned httpClient with set timeout if positive;\n// otherwise, returns the same client\nfunc cloneWithTimeout(httpClient *http.Client, t time.Duration) (*http.Client, error) {\n\tif httpClient == nil {\n\t\treturn nil, errors.New(\"nil HTTP client\")\n\t} else if httpClient.Transport == nil {\n\t\treturn nil, errors.New(\"nil HTTP client transport\")\n\t}\n\n\tif t.Nanoseconds() < 0 {\n\t\treturn httpClient, nil\n\t}\n\n\ttr, ok := httpClient.Transport.(*http.Transport)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unexpected HTTP transport: %T\", httpClient.Transport)\n\t}\n\n\t// copy all public fields, to avoid copying transient state and locks\n\tntr := &http.Transport{\n\t\tProxy:                  tr.Proxy,\n\t\tDialContext:            tr.DialContext,\n\t\tDial:                   tr.Dial,\n\t\tDialTLS:                tr.DialTLS,\n\t\tTLSClientConfig:        tr.TLSClientConfig,\n\t\tTLSHandshakeTimeout:    tr.TLSHandshakeTimeout,\n\t\tDisableKeepAlives:      tr.DisableKeepAlives,\n\t\tDisableCompression:     tr.DisableCompression,\n\t\tMaxIdleConns:           tr.MaxIdleConns,\n\t\tMaxIdleConnsPerHost:    tr.MaxIdleConnsPerHost,\n\t\tMaxConnsPerHost:        tr.MaxConnsPerHost,\n\t\tIdleConnTimeout:        tr.IdleConnTimeout,\n\t\tResponseHeaderTimeout:  tr.ResponseHeaderTimeout,\n\t\tExpectContinueTimeout:  tr.ExpectContinueTimeout,","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/api/api.go#L391-L427","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nclient.HttpClient.Transport = otelhttp.NewTransport(http.DefaultTransport)\nnodeClient, err := apiClient.GetNodeClientWithTimeout(nodeID, 5*time.Second) // panics into error\n// after\ntr := http.DefaultTransport.(*http.Transport).Clone()\nclient.HttpClient.Transport = tr // use context for tracing instead\nnodeClient, err := apiClient.GetNodeClientWithTimeout(nodeID, 5*time.Second)","handlingStrategy":"validation","validationCode":"if cfg.HttpClient != nil && cfg.HttpClient.Transport != nil {\n\tif _, ok := cfg.HttpClient.Transport.(*http.Transport); !ok {\n\t\treturn errors.New(\"Nomad client requires *http.Transport\")\n\t}\n}","typeGuard":"func isHTTPTransport(c *api.Config) bool {\n\t_, ok := c.HttpClient.Transport.(*http.Transport)\n\treturn ok\n}","tryCatchPattern":"nodeClient, err := client.GetNodeClientWithTimeout(nodeID, 5*time.Second)\nif err != nil && strings.Contains(err.Error(), \"unexpected HTTP transport\") {\n\treturn fmt.Errorf(\"replace custom RoundTripper with *http.Transport: %w\", err)\n}","preventionTips":["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"],"tags":["http","configuration","transport","nomad-api"],"backgroundTag":"invalid-http-transport","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}