docker/cli · warning

timed out waiting for device token

Error message

timed out waiting for device token

What it means

ErrTimeout, returned by WaitForDeviceToken when the device-code OAuth flow's expiry timer fires before the user completed authentication in their browser. Auth0 grants a limited window (ExpiryDuration from the device-code response) to authorize the device; if that elapses, polling stops and this error is returned.

Solutions

  1. Re-initiate the login flow to get a fresh device code and complete browser auth promptly.
  2. Open the verification URL and enter the code immediately after it is displayed.
  3. If expiry is consistently too short, check network connectivity and that the browser can reach the tenant.

Example fix

# before: docker login  # device code shown, user waited too long -> ErrTimeout
# after:  docker login   # re-run; open the printed URL and authorize within the time window
Defensive patterns

Strategy: retry

Validate before calling

// Before starting the device flow, you cannot extend the tenant expiry,
// but you can guard against background contexts that cancel early:
func ensureLoginContext(ctx context.Context) (context.Context, context.CancelFunc) {
	return context.WithTimeout(ctx, 15*time.Minute)
}

Type guard

// Detect the timeout sentinel
func isDeviceTokenTimeout(err error) bool {
	return errors.Is(err, api.ErrTimeout)
}

Try / catch

// Re-initiate the device flow on timeout (user-driven retry)
state, err := api.GetDeviceCode(ctx, audience)
if err != nil { return err }
tok, err := api.WaitForDeviceToken(ctx, state)
if errors.Is(err, api.ErrTimeout) {
    // prompt user to retry promptly
    state, err = api.GetDeviceCode(ctx, audience)
    if err != nil { return err }
    tok, err = api.WaitForDeviceToken(ctx, state)
}
return tok, err

Prevention

When it happens

Trigger: Initiating a device-code login (e.g. docker login with the OAuth flow), receiving the device code and URL, but not visiting the URL / entering the code before the tenant's expiry window closes. The timeout.C branch at api.go:136-138 fires.

Common situations: User stepped away from the terminal; the device code expired before the browser auth completed; slow network or browser delays; the verification URL was not opened in time.

Understand the failure class

Related errors


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

Appendix: source

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

	// ClientID is the client ID for the application to auth with the tenant.
	ClientID string
	// Scopes are the scopes that are requested during the device auth flow.
	Scopes []string
}

// TokenResponse represents the response of the /oauth/token route.
type TokenResponse struct {
	AccessToken      string  `json:"access_token"`
	IDToken          string  `json:"id_token"`
	RefreshToken     string  `json:"refresh_token"`
	Scope            string  `json:"scope"`
	ExpiresIn        int     `json:"expires_in"`
	TokenType        string  `json:"token_type"`
	Error            *string `json:"error,omitempty"`
	ErrorDescription string  `json:"error_description,omitempty"`
}

var ErrTimeout = errors.New("timed out waiting for device token")

// GetDeviceCode initiates the device-code auth flow with the tenant.
// The state returned contains the device code that the user must use to
// authenticate, as well as the URL to visit, etc.
func (a API) GetDeviceCode(ctx context.Context, audience string) (State, error) {
	data := url.Values{
		"client_id": {a.ClientID},
		"audience":  {audience},
		"scope":     {strings.Join(a.Scopes, " ")},
	}

	deviceCodeURL := a.TenantURL + "/oauth/device/code"
	resp, err := postForm(ctx, deviceCodeURL, strings.NewReader(data.Encode()))
	if err != nil {
		return State{}, err
	}
	defer func() {
		_ = resp.Body.Close()

View on GitHub (pinned to 4f84911bfe)