chatboxai/chatbox · error · Error

${response.status} ${response.statusText}: ${text}

Error message

${response.status} ${response.statusText}: ${text}

What it means

Generic HTTP failure thrown by the internal fetchJson() helper used by both startDeviceFlow() (POST /login/device/code) and waitForToken() (POST /login/oauth/access_token). The message combines status, statusText, and body so the underlying GitHub response (e.g. 404, 422 JSON, rate-limit text) is visible. Because GitHub returns pending/slow_down as HTTP 200 with an error field, a throw from fetchJson always indicates a genuine HTTP-level failure, not the expected polling states.

Source

Thrown at src/main/oauth/providers/github-copilot.ts:23

const CLIENT_ID = decode('T3YyM2xpOHR3ZVF3Nm9kV1FlYno=')
const GITHUB_DOMAIN = 'github.com'

// Pending device flow state
let pendingDeviceCode: string | null = null
let pendingInterval = 5

function getUrls(domain: string) {
  return {
    deviceCodeUrl: `https://${domain}/login/device/code`,
    accessTokenUrl: `https://${domain}/login/oauth/access_token`,
  }
}

async function fetchJson(url: string, init: RequestInit): Promise<unknown> {
  const response = await fetch(url, init)
  if (!response.ok) {
    const text = await response.text()
    throw new Error(`${response.status} ${response.statusText}: ${text}`)
  }
  return response.json()
}

export const githubCopilotOAuthProvider: DeviceCodeOAuthProvider = {
  kind: 'device-code',
  providerId: 'github-copilot',
  name: 'GitHub Copilot',

  async startDeviceFlow() {
    const urls = getUrls(GITHUB_DOMAIN)
    const data = (await fetchJson(urls.deviceCodeUrl, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({

View on GitHub (pinned to 81571269ad)

Solutions

  1. Parse the leading status code from the message — 404/422 on /login/device/code usually means a bad CLIENT_ID or GITHUB_DOMAIN.
  2. For 'expired_token' from the polling endpoint, restart with startDeviceFlow() to get a fresh device_code.
  3. For 429, respect Retry-After and back off; for 5xx, retry with jitter.
  4. Confirm the CLIENT_ID decode still yields the expected GitHub OAuth App id.

Example fix

// before
const data = (await fetchJson(urls.deviceCodeUrl, init)) as {...}

// after
let data
try {
  data = (await fetchJson(urls.deviceCodeUrl, init)) as {...}
} catch (e) {
  const msg = String(e)
  if (/\b404\b|\b422\b/.test(msg)) throw new Error('GitHub rejected the client_id or domain. Check CLIENT_ID.')
  throw e
}
Defensive patterns

Strategy: try-catch

Type guard

function isHttpFailure(e: unknown): e is Error {
  return e instanceof Error && /^\d{3}\s+/.test(e.message)
}

Try / catch

try {
  return await provider.startDeviceFlow()
} catch (e) {
  const msg = String(e)
  if (/\b429\b/.test(msg)) {
  await new Promise(r => setTimeout(r, backoffMs()))
  return await provider.startDeviceFlow()
  }
  if (/\b404\b|\b422\b/.test(msg)) throw new Error('GitHub rejected CLIENT_ID or domain — check config.')
  throw e
}

Prevention

When it happens

Trigger: Invalid or revoked CLIENT_ID constant; wrong GITHUB_DOMAIN; the device_code has expired (422 'expired_token') or is invalid; rate limited (429); GitHub API outage (5xx); malformed request body (422 unsupported_grant_type).

Common situations: CLIENT_ID base64 constant drifted after GitHub rotated it; user waited past the device-code lifetime then the polling hit an expired device_code; corporate proxy returning a captive-portal HTML page (status 200 but non-JSON, or a 4xx).

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/b215acad38e41867. Report an issue: GitHub.