sipeed/picoclaw · error

parsing device code response: %w

Error message

parsing device code response: %w

What it means

Thrown by RequestDeviceCode (pkg/auth/oauth.go:276) when the POST to {Issuer}/api/accounts/deviceauth/usercode returned HTTP 200 but parseDeviceCodeResponse could not decode the body. The parser expects JSON with device_auth_id, user_code and an interval that is a number, numeric string, or null; any JSON decode failure or a bad interval type propagates here.

Source

Thrown at pkg/auth/oauth.go:276

		"application/json",
		strings.NewReader(string(reqBody)),
	)
	if err != nil {
		return nil, fmt.Errorf("requesting device code: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading device code response: %w", err)
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("device code request failed: %s", string(body))
	}

	deviceResp, err := parseDeviceCodeResponse(body)
	if err != nil {
		return nil, fmt.Errorf("parsing device code response: %w", err)
	}

	if deviceResp.Interval < 1 {
		deviceResp.Interval = 5
	}

	return &DeviceCodeInfo{
		DeviceAuthID: deviceResp.DeviceAuthID,
		UserCode:     deviceResp.UserCode,
		VerifyURL:    cfg.Issuer + "/codex/device",
		Interval:     deviceResp.Interval,
	}, nil
}

// PollDeviceCodeOnce makes a single poll attempt to check if the user has authenticated.
// Returns (credential, nil) on success, (nil, nil) if still pending, or (nil, err) on failure.
func PollDeviceCodeOnce(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*AuthCredential, error) {
	return pollDeviceCode(cfg, deviceAuthID, userCode)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Curl the exact endpoint to inspect the payload: curl -sS -X POST -H 'Content-Type: application/json' -d '{"client_id":"..."}' $ISSUER/api/accounts/deviceauth/usercode
  2. Verify cfg.Issuer is the OAuth issuer root (e.g. https://auth.openai.com) with no trailing slash, path, or /v1 suffix
  3. Check the interval field type in the response: must be a number, numeric string, or null (not float/bool/object)
  4. If the provider API changed shape, update parseDeviceCodeResponse field tags or the endpoint path in pkg/auth/oauth.go
  5. Log the raw body next to the parse error so future failures show what the server actually returned

Example fix

// before
deviceResp, err := parseDeviceCodeResponse(body)
if err != nil {
	return nil, fmt.Errorf("parsing device code response: %w", err)
}

// after (include raw body for diagnosis, truncated)
deviceResp, err := parseDeviceCodeResponse(body)
if err != nil {
	snippet := body
	if len(snippet) > 512 {
		snippet = snippet[:512]
	}
	return nil, fmt.Errorf("parsing device code response: %w (body: %s)", err, snippet)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate issuer shape before the call
if u, err := url.Parse(cfg.Issuer); err != nil || u.Scheme == "" || u.Host == "" {
	return fmt.Errorf("invalid issuer URL: %q", cfg.Issuer)
}

Type guard

func isDeviceCodeParseError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "parsing device code response")
}

Try / catch

info, err := auth.RequestDeviceCode(cfg)
if err != nil {
	if isDeviceCodeParseError(err) {
		// log issuer and endpoint; likely schema/issuer mismatch — do not blind-retry
		log.Printf("device code schema mismatch from %s: %v", cfg.Issuer, err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling RequestDeviceCode(cfg) where the issuer endpoint replies 200 with: malformed JSON, HTML instead of JSON, missing/mis-typed fields (e.g. interval as 5.5, true, or an object), or an unexpected API envelope that fails json.Unmarshal/parseFlexibleInt.

Common situations: Issuer base URL misconfigured to a host that serves a 200 status page; a corporate proxy or captive portal injecting HTML; the provider renaming fields or shipping a new device-auth API version; middleware returning an empty 200 body.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/0f0886919a4d28d9. Report an issue: GitHub.