Crosstalk-Solutions/project-nomad · error · Error
No token returned from ${registry}
Error message
No token returned from ${registry} What it means
Thrown by ContainerRegistryService.getToken when the registry's token endpoint responds HTTP 200 but the JSON body contains neither 'token' nor 'access_token' (or they're empty strings). The endpoint answered successfully but didn't grant an anonymous pull token for the requested scope.
Source
Thrown at admin/app/services/container_registry_service.ts:108
if (registry === 'registry-1.docker.io') {
tokenUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${fullName}:pull`
} else if (registry === 'ghcr.io') {
tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${fullName}:pull`
} else {
// For other registries, try the standard v2 token endpoint
tokenUrl = `https://${registry}/token?service=${registry}&scope=repository:${fullName}:pull`
}
const response = await this.fetchWithRetry(tokenUrl)
if (!response.ok) {
throw new Error(`Failed to get auth token from ${registry}: ${response.status}`)
}
const data = (await response.json()) as { token?: string; access_token?: string }
const token = data.token || data.access_token || ''
if (!token) {
throw new Error(`No token returned from ${registry}`)
}
// Cache for 5 minutes (tokens usually last longer, but be conservative)
this.tokenCache.set(cacheKey, {
token,
expiresAt: Date.now() + 5 * 60 * 1000,
})
return token
}
/**
* List all tags for a given image from the registry.
*/
async listTags(parsed: ParsedImageReference): Promise<string[]> {
const token = await this.getToken(parsed.registry, parsed.fullName)
const allTags: string[] = []
let url = `https://${parsed.registry}/v2/${parsed.fullName}/tags/list?n=1000`View on GitHub (pinned to 0bd1c6f4f9)
Solutions
- curl the token URL directly and inspect the JSON body to see what the registry actually returns
- If the repository is private, add credentials (Authorization header) to the token request or skip anonymous auth for that registry
- Verify the image name/scope string is exactly right (namespace/repo, correct tag/digest usage)
- If a proxy is mangling responses, whitelist the registry domain or configure the proxy env for fetchWithRetry
- Handle this error by falling back to unauthenticated manifest requests where the registry allows it
Example fix
// before
const token = data.token || data.access_token || ''
if (!token) {
throw new Error(`No token returned from ${registry}`)
}
// after
const token = data.token || data.access_token || ''
if (!token) {
logger.warn(`[ContainerRegistryService] Empty token body from ${registry}: ${JSON.stringify(data).slice(0, 200)}`)
return '' // some registries allow anonymous pulls; let the manifest request decide
} Defensive patterns
Strategy: fallback
Validate before calling
const res = await fetch(tokenUrl) const body = await res.json().catch(() => null) if (!body || !(body.token || body.access_token)) planAnonymousPull(registry) // no token path
Type guard
function hasRegistryToken(data: unknown): data is { token: string } | { access_token: string } {
if (typeof data !== 'object' || data === null) return false
const d = data as Record<string, unknown>
return (typeof d.token === 'string' && d.token.length > 0) || (typeof d.access_token === 'string' && d.access_token.length > 0)
} Try / catch
try { token = await svc.getToken(registry, repo) } catch (e) { if (e.message === `No token returned from ${registry}`) { token = ''; proceedAnonymousPull() } else throw e } Prevention
- Fall back to anonymous (no Authorization header) manifest requests when the token body is empty
- Validate image names before building the scope string
- Log the raw token response body when fields are missing
- Add credentials to the token request for private repositories
When it happens
Trigger: Requesting a pull token for a private repository without credentials — some registries return 200 with an empty/error JSON instead of a 401; a proxy or login page intercepting the request and returning HTML/JSON without token fields; or a mistyped repository scope.
Common situations: Listing/pulling private images anonymously through a UI that assumes public images, captive-portal or corporate proxies rewriting responses, registries whose token endpoint returns errors with 200 status, or malformed image names producing an invalid scope.
Related errors
- Failed to get auth token from ${registry}: ${response.status
- sysbench disk-write benchmark produced no parseable MiB/s —
- Sysbench command failed: ${error.message}
- recreated container ${readiness.reason}
- Failed to fetch available versions for this service.
AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27).
Data as JSON: /api/errors/023abeb282cdfa60.
Report an issue: GitHub.