docker/cli · error

failed to decode response

Error message

failed to decode response: %w

What it means

Returned by getDeviceToken() when json.NewDecoder(resp.Body).Decode(&res) fails. The token endpoint returned an HTTP response (no transport error, status not checked here because 403 authorization_pending is normal) but the body is not valid JSON or does not match TokenResponse.

Solutions

  1. Confirm the TenantURL is correct and reachable directly (curl the /oauth/token endpoint).
  2. Bypass captive portals / proxies that rewrite responses to HTML.
  3. Retry the login flow once transient intermediary issues clear.
  4. Update the CLI to match the current Hub OAuth contract.

Example fix

# before: captive portal returns HTML
docker login
# after: connect to real network / configure proxy auth
wifi-connect trusted-network
docker login
Defensive patterns

Strategy: try-catch

Try / catch

var se *json.SyntaxError
var te *json.UnmarshalTypeError
switch {
case errors.As(err, &se):  // body not JSON (captive portal)
case errors.As(err, &te): // schema mismatch
}

Prevention

When it happens

Trigger: The OAuth tenant returns HTML/garbage (captive portal, proxy error page, 5xx with text body) instead of the expected JSON token/error document during the device-code grant polling.

Common situations: Captive Wi-Fi portal intercepting the POST; reverse proxy returning an HTML error; tenant endpoint changed schema; truncated response from a flaky proxy.

Understand the failure class

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/237668645fb0262a. Report an issue: GitHub.

Appendix: source

Thrown at internal/oauth/api/api.go:184

		"device_code": {state.DeviceCode},
	}
	oauthTokenURL := a.TenantURL + "/oauth/token"

	resp, err := postForm(ctx, oauthTokenURL, strings.NewReader(data.Encode()))
	if err != nil {
		return TokenResponse{}, fmt.Errorf("failed to get tokens: %w", err)
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	// this endpoint returns a 403 with an `authorization_pending` error until the
	// user has authenticated, so we don't check the status code here and instead
	// decode the response and check for the error.
	var res TokenResponse
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return res, fmt.Errorf("failed to decode response: %w", err)
	}

	return res, nil
}

// RevokeToken revokes a refresh token with the tenant so that it can no longer
// be used to get new tokens.
func (a API) RevokeToken(ctx context.Context, refreshToken string) error {
	data := url.Values{
		"client_id": {a.ClientID},
		"token":     {refreshToken},
	}

	revokeURL := a.TenantURL + "/oauth/revoke"
	resp, err := postForm(ctx, revokeURL, strings.NewReader(data.Encode()))
	if err != nil {
		return err
	}

View on GitHub (pinned to 4f84911bfe)