{"record":{"id":"7c0d3908591c0db3","repo":"Crosstalk-Solutions/project-nomad","slug":"failed-to-get-auth-token-from-registry-respo","errorCode":null,"errorMessage":"Failed to get auth token from ${registry}: ${response.status}","messagePattern":"Failed to get auth token from (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"admin/app/services/container_registry_service.ts","lineNumber":101,"sourceCode":"    const cacheKey = `${registry}/${fullName}`\n    const cached = this.tokenCache.get(cacheKey)\n    if (cached && cached.expiresAt > Date.now()) {\n      return cached.token\n    }\n\n    let tokenUrl: string\n    if (registry === 'registry-1.docker.io') {\n      tokenUrl = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${fullName}:pull`\n    } else if (registry === 'ghcr.io') {\n      tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${fullName}:pull`\n    } else {\n      // For other registries, try the standard v2 token endpoint\n      tokenUrl = `https://${registry}/token?service=${registry}&scope=repository:${fullName}:pull`\n    }\n\n    const response = await this.fetchWithRetry(tokenUrl)\n    if (!response.ok) {\n      throw new Error(`Failed to get auth token from ${registry}: ${response.status}`)\n    }\n\n    const data = (await response.json()) as { token?: string; access_token?: string }\n    const token = data.token || data.access_token || ''\n\n    if (!token) {\n      throw new Error(`No token returned from ${registry}`)\n    }\n\n    // Cache for 5 minutes (tokens usually last longer, but be conservative)\n    this.tokenCache.set(cacheKey, {\n      token,\n      expiresAt: Date.now() + 5 * 60 * 1000,\n    })\n\n    return token\n  }\n","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/container_registry_service.ts#L83-L119","documentation":"Thrown by ContainerRegistryService.getToken when the HTTP response from a registry's v2 token endpoint (e.g. https://<registry>/token?service=...&scope=repository:<name>:pull) returns a non-OK status, after fetchWithRetry retries were exhausted. The status code is embedded in the message (401, 404, 429, 5xx each imply different causes).","triggerScenarios":"Pulling metadata for an image on a registry whose auth endpoint rejects the request: 401 for an unknown/forbidden repository on Docker Hub, 404 when the token endpoint path is wrong for that registry (non-standard registries), 429 rate limiting, or 5xx during registry outages.","commonSituations":"Private/not-well-known registries that don't implement the standard /token endpoint, Docker Hub rate limits on anonymous token requests, corporate proxies returning 407/401, misspelled image names, or transient registry incidents.","solutions":["Map the embedded status: 401/403 → repository doesn't exist or needs credentials (pass an auth token or Bearer); 404 → registry needs a custom token URL, extend the per-registry branch; 429 → back off and respect rate limits; 5xx → retry later","Test the URL manually: curl 'https://<registry>/token?service=<registry>&scope=repository:<image>:pull' to see the raw response","For private registries, supply credentials to the fetch instead of anonymous token requests","Add per-registry overrides in getToken's registry-specific branches for non-standard auth endpoints"],"exampleFix":"// before\nconst response = await this.fetchWithRetry(tokenUrl)\nif (!response.ok) {\n  throw new Error(`Failed to get auth token from ${registry}: ${response.status}`)\n}\n\n// after\nconst response = await this.fetchWithRetry(tokenUrl)\nif (!response.ok) {\n  const body = await response.text().catch(() => '')\n  throw new Error(`Failed to get auth token from ${registry}: ${response.status} ${body.slice(0, 200)}`)\n}","handlingStrategy":"retry","validationCode":"const res = await fetch(tokenUrl)\nif (res.status === 404 || res.status === 401) configureCustomAuthFor(registry) // avoid guaranteed-failing calls\nif (res.status === 429) await waitForRateLimitWindow()","typeGuard":"function isRetryableTokenStatus(status: number): boolean {\n  return status === 429 || status >= 500\n}","tryCatchPattern":"try { const t = await registrySvc.getToken(...) } catch (e) { if (/Failed to get auth token.*:(429|5\\d\\d)/.test(e.message)) { await sleep(backoff); retry() } else if (/:(401|403)/.test(e.message)) promptForCredentials() else throw e }","preventionTips":["Cache tokens (the service already caches 5 min) to reduce endpoint hits","Add per-registry auth URL overrides for non-standard registries","Respect Retry-After on 429s in fetchWithRetry","Include response body in the thrown error for easier triage"],"tags":["docker","registry","oauth-token","http","authentication"],"backgroundTag":"registry-auth-token-request-failed","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}