diff --git a/packages/opencode/src/plugin/copilot.ts b/packages/opencode/src/plugin/copilot.ts index 17ce9debc7d..776b70fe91a 100644 --- a/packages/opencode/src/plugin/copilot.ts +++ b/packages/opencode/src/plugin/copilot.ts @@ -3,6 +3,9 @@ import { Installation } from "@/installation" import { iife } from "@/util/iife" const CLIENT_ID = "Ov23li8tweQw6odWQebz" +// Add a small safety buffer when polling to avoid hitting the server +// slightly too early due to clock skew / timer drift. +const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000 // 3 seconds function normalizeDomain(url: string) { return url.replace(/^https?:\/\//, "").replace(/\/$/, "") @@ -204,6 +207,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { const data = (await response.json()) as { access_token?: string error?: string + interval?: number } if (data.access_token) { @@ -230,13 +234,29 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { } if (data.error === "authorization_pending") { - await new Promise((resolve) => setTimeout(resolve, deviceData.interval * 1000)) + await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS) + continue + } + + if (data.error === "slow_down") { + // Based on the RFC spec, we must add 5 seconds to our current polling interval. + // (See https://www.rfc-editor.org/rfc/rfc8628#section-3.5) + let newInterval = (deviceData.interval + 5) * 1000 + + // GitHub OAuth API may return the new interval in seconds in the response. + // We should try to use that if provided with safety margin. + const serverInterval = data.interval + if (serverInterval && typeof serverInterval === 'number' && serverInterval > 0) { + newInterval = serverInterval * 1000 + } + + await Bun.sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS) continue } if (data.error) return { type: "failed" as const } - await new Promise((resolve) => setTimeout(resolve, deviceData.interval * 1000)) + await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS) continue } },