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
- Check and fix the token pool configuration (e.g. GH_TOKEN_POOL env var) ensuring valid, comma-separated tokens
- Verify each token's validity with curl -H "Authorization: token <t>" https://api.github.com
- Replace tokens that have expired or been revoked; generate new personal access tokens
- Wait for GitHub rate limits to reset (tokens restore hourly) and retry
- 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
- Configure multiple valid tokens in the token pool for self-hosted deployments
- Rotate tokens before they expire and remove revoked ones
- Monitor GitHub rate limits per token and alert when exhausted
- Validate the pool env var format (comma-separated, no empty entries) at server startup
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
- Unable to select next Libraries.io token from pool
- bucket "${bucket}" not found
- job not found
- gist not found
- invalid branch
AI-assisted analysis of badges/shields@766fd8bc89 (2026-08-30).
Data as JSON: /api/errors/1e7664e7d03f82c5.
Report an issue: GitHub.