can1357/oh-my-pi · error · AIError.OAuthError

Failed to initiate device authorization

Error message

Failed to initiate device authorization: ${initiateResponse.status}

What it means

Kilo device-authorization initiation failed with any status other than 2xx and other than 429 (which has its own message). loginKilo wraps the HTTP status into this OAuthError with kind "device-auth" so the caller knows the login could not even start. The status field on the error carries the actual HTTP response code.

Solutions

  1. Inspect the error's status field to identify the HTTP cause (5xx = server-side, retry later; 4xx = check endpoint/auth).
  2. Check Kilo's service status — 5xx responses usually mean a temporary outage.
  3. Verify network/proxy settings (HTTPS_PROXY, corporate firewall) allow access to the device-auth endpoint.
  4. Update the library — the base URL or API contract may have changed upstream.

Example fix

// before
await loginKilo(callbacks); // throws on 500 with bare message
// after
try {
  await loginKilo(callbacks);
} catch (err) {
  if (AIError.OAuthError.is(err) && err.status >= 500) {
    await Bun.sleep(30_000);
    await loginKilo(callbacks); // retry on server errors
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// probe the endpoint before running the full flow
const probe = await fetch(KILO_DEVICE_AUTH_BASE_URL, { method: "HEAD" }).catch(() => null);
if (!probe || probe.status >= 500) throw new Error("Kilo device-auth endpoint unavailable; retry later");

Try / catch

try {
  await loginKilo(callbacks);
} catch (err) {
  if (err instanceof AIError.OAuthError && err.message.startsWith("Failed to initiate")) {
    const status = (err as { status?: number }).status;
    if (status && status >= 500) return retryWithBackoff(() => loginKilo(callbacks));
    // 4xx: surface to user, don't blindly retry
  }
  throw err;
}

Prevention

When it happens

Trigger: The POST to KILO_DEVICE_AUTH_BASE_URL to create a device code returns a non-ok, non-429 response — e.g. 500 server error, 502/503 from a proxy, 401/403 due to blocked endpoint, or a 404 because the endpoint path changed.

Common situations: Kilo service outage or maintenance; corporate proxy/firewall intercepting the request; DNS or base-URL misconfiguration pointing at the wrong host; API version change on the provider side.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/d37196dda1055377. Report an issue: GitHub.

Appendix: source

Thrown at packages/ai/src/registry/kilo.ts:35

	token?: string;
}

export async function loginKilo(callbacks: OAuthController): Promise<OAuthCredentials> {
	const fetchImpl = callbacks.fetch ?? fetch;
	const initiateResponse = await fetchImpl(`${KILO_DEVICE_AUTH_BASE_URL}/codes`, {
		method: "POST",
		headers: { "Content-Type": "application/json" },
	});

	if (!initiateResponse.ok) {
		if (initiateResponse.status === 429) {
			throw new AIError.OAuthError("Too many pending authorization requests. Please try again later.", {
				kind: "polling",
				provider: "kilo",
				status: initiateResponse.status,
			});
		}
		throw new AIError.OAuthError(`Failed to initiate device authorization: ${initiateResponse.status}`, {
			kind: "device-auth",
			provider: "kilo",
			status: initiateResponse.status,
		});
	}

	const initiateData = (await initiateResponse.json()) as KiloDeviceAuthCodeResponse;
	const userCode = initiateData.code;
	const verificationUrl = initiateData.verificationUrl;
	const expiresInSeconds = initiateData.expiresIn;
	if (!userCode || !verificationUrl || typeof expiresInSeconds !== "number" || expiresInSeconds <= 0) {
		throw new AIError.OAuthError("Kilo device authorization response missing required fields", {
			kind: "validation",
			provider: "kilo",
		});
	}

	callbacks.onAuth?.({

View on GitHub (pinned to 9690622007)