larksuite/cli · error

proxy plugin transport unavailable: http.DefaultTransport is

Error message

proxy plugin transport unavailable: http.DefaultTransport is %T, want *http.Transport

What it means

The proxy plugin transport builder expects http.DefaultTransport to be a *http.Transport so it can clone it; a test or embedding host replaced it with a custom RoundTripper implementation. The builder fails closed with a blocked transport rather than allowing direct egress, since it cannot safely apply proxy settings to an unknown RoundTripper.

Source

Thrown at internal/transport/transport.go:33

var proxyPluginTransport = sync.OnceValue(buildProxyPluginTransport)

// cachedBlockedTransport is a fail-closed transport cached on first use when
// the proxy plugin config exists but is invalid. This avoids cloning
// http.DefaultTransport on every pluginTransport call.
var cachedBlockedTransport = sync.OnceValue(buildBlockedTransport)

func buildBlockedTransport() http.RoundTripper {
	return failClosedTransport(fmt.Errorf("proxy plugin config is invalid: %w", loadErr))
}

func buildProxyPluginTransport() http.RoundTripper {
	def, ok := http.DefaultTransport.(*http.Transport)
	if !ok {
		// Cannot clone the stdlib transport. Fail closed with a concrete
		// *http.Transport (not a bare RoundTripper) so downcasting callers such
		// as Fallback cannot silently degrade this into a
		// direct-egress transport.
		return failClosedTransport(fmt.Errorf("proxy plugin transport unavailable: http.DefaultTransport is %T, want *http.Transport", http.DefaultTransport))
	}

	cfg, err := Load()
	if err != nil {
		// Fail closed: config file exists but is malformed/unreadable — do not
		// silently fall back to direct egress.
		return blockedTransport(def, fmt.Errorf("proxy plugin config is invalid: %w", err))
	}
	if cfg == nil || !cfg.Enabled() {
		return def
	}
	t, err := cfg.ApplyToTransport(def)
	if err != nil {
		// Fail closed: do not silently fall back to direct egress when the
		// operator explicitly enabled proxy plugin mode.
		return blockedTransport(def, fmt.Errorf("proxy plugin enabled but config is invalid: %w", err))
	}
	return t

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Remove or defer the http.DefaultTransport override so the stdlib *http.Transport is in place when the CLI initializes.
  2. If the override is required, install a *http.Transport (e.g. a clone of the default) instead of a bare RoundTripper.
  3. Restore http.DefaultTransport with t.Cleanup/defer in tests before exercising CLI transport setup.

Example fix

// before
http.DefaultTransport = myMetricsRoundTripper{}
// after
if def, ok := http.DefaultTransport.(*http.Transport); ok {
	cloned := def.Clone()
	cloned.Proxy = nil
	http.DefaultTransport = &instrumentedTransport{base: cloned}
}
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := http.DefaultTransport.(*http.Transport); !ok {
	log.Fatal("http.DefaultTransport must remain a *http.Transport for the CLI proxy plugin")
}

Type guard

func defaultTransportIsClonable() bool {
	_, ok := http.DefaultTransport.(*http.Transport)
	return ok
}

Try / catch

if tr, err := pluginTransport(); err != nil && strings.Contains(err.Error(), "want *http.Transport") {
	log.Fatalf("remove the http.DefaultTransport override in this process: %v", err)
}

Prevention

When it happens

Trigger: Code (usually tests like TestBuildProxyPluginTransport_NonTransportDefaultFailsClosed, or a host app) assigns a non-*http.Transport to http.DefaultTransport, then the proxy plugin path builds its transport.

Common situations: Libraries that globally override http.DefaultTransport (metrics, mocking, custom dialers) running in-process with the CLI; tests swapping DefaultTransport without restoring it.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/6017693e10c50e24. Report an issue: GitHub.