badges/shields · critical · ImproperlyConfigured

Unable to select next GitHub token from pool

Error message

Unable to select next GitHub token from pool

What it means

Thrown by GithubApiProvider.fetch when running in TOKEN_POOL auth mode and pool.next() throws — i.e. the token pool could not supply any usable GitHub token (all tokens exhausted/blocked/uninitialized). The provider wraps it as ImproperlyConfigured 'Unable to select next GitHub token from pool', indicating a server-side configuration problem rather than a client error.

Source

Thrown at services/github/github-api-provider.js:221

        )})`,
      )
      pool.endBatchFor(token)
    }
  }

  async fetch(requestFetcher, url, options = {}) {
    const { baseUrl } = this

    let token
    let tokenString
    let pool
    if (this.authType === this.constructor.AUTH_TYPES.TOKEN_POOL) {
      pool = this.poolForUrl(url)
      try {
        token = pool.next()
      } catch (e) {
        log.error(e)
        throw new ImproperlyConfigured({
          prettyMessage: 'Unable to select next GitHub token from pool',
        })
      }
      tokenString = token.id
    } else if (this.authType === this.constructor.AUTH_TYPES.GLOBAL_TOKEN) {
      tokenString = this.globalToken
    }

    const mergedOptions = {
      ...options,
      ...{
        headers: {
          'User-Agent': userAgent,
          'X-GitHub-Api-Version': this.restApiVersion,
          ...options.headers,
        },
      },
    }

View on GitHub (pinned to 766fd8bc89)

Solutions

  1. Check and fix the token pool configuration (e.g. GH_TOKEN_POOL env var) ensuring valid, comma-separated tokens
  2. Verify each token's validity with curl -H "Authorization: token <t>" https://api.github.com
  3. Replace tokens that have expired or been revoked; generate new personal access tokens
  4. Wait for GitHub rate limits to reset (tokens restore hourly) and retry
  5. If only one token is needed, switch config to global token mode

Example fix

// before
GH_TOKEN_POOL=
// after
GH_TOKEN_POOL=ghp_token1,ghp_token2,ghp_token3
Defensive patterns

Strategy: retry

Validate before calling

const tokens = (process.env.GH_TOKEN_POOL || '').split(',').filter(t => t.length > 0);if (tokens.length === 0) { throw new Error('GH_TOKEN_POOL is empty; add at least one valid GitHub token'); }for (const t of tokens) { const r = await fetch('https://api.github.com/rate_limit', { headers: { Authorization: `token ${t}` } }); if (!r.ok) console.warn('token invalid:', t.slice(0, 8)); }

Type guard

const hasUsableTokens = (tokens) => Array.isArray(tokens) && tokens.every(t => typeof t === 'string' && t.startsWith('ghp_') || typeof t === 'string' && t.startsWith('github_pat_'));

Try / catch

try { return await githubApi.fetch(url); } catch (e) { if (String(e.message).includes('next GitHub token')) { await sleep(60_000); return retryWithBackoff(() => githubApi.fetch(url)); } throw e; }

Prevention

When it happens

Trigger: All tokens in the configured pool have hit rate limits or been invalidated so the pool's rotation throws; the pool was constructed with an empty/invalid token list in the badge server's configuration.

Common situations: Self-hosted shields.io deployments with GH_TOKEN_POOL unset, empty, or containing only revoked tokens; heavy usage draining all pooled tokens' rate limits; misencoded pool env var (wrong separator) yielding no valid tokens.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of badges/shields@766fd8bc89 (2026-08-30). Data as JSON: /api/errors/1e7664e7d03f82c5. Report an issue: GitHub.