sipeed/picoclaw · error

unsupported login method %q for provider %q

Error message

unsupported login method %q for provider %q

What it means

Returned as HTTP 400 by POST /api/oauth/login when the method passes provider normalization but is not in that provider's method matrix (oauthProviderMethods). The matrix is strict: openai supports browser, device_code, token; anthropic supports only token; google-antigravity supports only browser. The method string is lowercased and trimmed, but must otherwise match exactly (underscore in "device_code", not hyphen).

Source

Thrown at web/backend/api/oauth.go:199

	var req struct {
		Provider string `json:"provider"`
		Method   string `json:"method"`
		Token    string `json:"token"`
	}
	if err = json.Unmarshal(body, &req); err != nil {
		http.Error(w, fmt.Sprintf("invalid JSON: %v", err), http.StatusBadRequest)
		return
	}

	provider, err := normalizeOAuthProvider(req.Provider)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	method := strings.ToLower(strings.TrimSpace(req.Method))
	if !isOAuthMethodSupported(provider, method) {
		http.Error(
			w,
			fmt.Sprintf("unsupported login method %q for provider %q", method, provider),
			http.StatusBadRequest,
		)
		return
	}

	switch method {
	case oauthMethodToken:
		token := strings.TrimSpace(req.Token)
		if token == "" {
			http.Error(w, "token is required", http.StatusBadRequest)
			return
		}

		cred := &auth.AuthCredential{
			AccessToken: token,
			Provider:    provider,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Match the matrix exactly: openai → browser|device_code|token, anthropic → token, google-antigravity → browser.
  2. Read the "methods" array from GET /api/oauth/providers per provider and only render/offer those login options.
  3. Use the literal strings "browser", "device_code", "token" — note the underscore in device_code; the value is trimmed and lowercased but not otherwise normalized.
  4. If you believe a method should be supported (e.g. anthropic browser OAuth), it requires a backend change (oauthProviderMethods plus an oauthConfigForProvider entry), not a config tweak.

Example fix

// before
const method = provider === 'anthropic' ? 'device_code' : 'browser';
await fetch('/api/oauth/login', {method:'POST', body: JSON.stringify({provider, method})});
// -> 400 unsupported login method "device_code" for provider "anthropic"

// after
const supported = await getProviderMethods(provider); // from GET /api/oauth/providers
const method = supported.includes('device_code') ? 'device_code' : supported[0];
Defensive patterns

Strategy: validation

Validate before calling

const MATRIX = {
  'openai': ['browser', 'device_code', 'token'],
  'anthropic': ['token'],
  'google-antigravity': ['browser'],
};
function assertMethod(provider, method) {
  const m = String(method ?? '').trim().toLowerCase();
  if (!MATRIX[provider]?.includes(m)) throw new Error(`unsupported login method ${JSON.stringify(method)} for provider ${JSON.stringify(provider)}`);
  return m;
}

Type guard

function isMethodSupported(provider, method) {
  return (MATRIX[provider] ?? []).includes(String(method ?? '').trim().toLowerCase());
}

Prevention

When it happens

Trigger: POST /api/oauth/login with {"provider":"anthropic","method":"browser"} (anthropic has no browser flow), or {"provider":"openai","method":"device-code"} (hyphen instead of underscore), or {"provider":"google-antigravity","method":"device_code"} (antigravity is browser-only).

Common situations: Frontend that shows the same three login buttons for every provider; renaming method strings during an API migration; assuming device_code works everywhere because it works for OpenAI.

Related errors


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