{"record":{"id":"f7db0e0ee5e17a51","repo":"coleam00/Archon","slug":"user-fetch-failed","errorCode":"user_fetch_failed","errorMessage":"GET /user returned HTTP ${res.status}","messagePattern":"GET /user returned HTTP (.+?)","errorType":"error_code","errorClass":"DeviceFlowError","httpStatus":null,"severity":"error","filePath":"packages/core/src/github-auth/device-flow.ts","lineNumber":186,"sourceCode":"    client_id: clientId,\n    grant_type: 'refresh_token',\n    refresh_token: refreshToken,\n  });\n  if (data.error) throw new DeviceFlowError(data.error);\n  return data;\n}\n\n/** Fetch the authenticated user's profile (id is the no-reply-email anchor). */\nexport async function fetchGithubUser(accessToken: string): Promise<GithubUserProfile> {\n  const res = await fetch(USER_URL, {\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      Accept: 'application/vnd.github+json',\n      'User-Agent': 'archon',\n    },\n  });\n  if (!res.ok) {\n    throw new DeviceFlowError('user_fetch_failed', `GET /user returned HTTP ${res.status}`);\n  }\n  const raw = (await res.json()) as {\n    id: number;\n    login: string;\n    name?: string | null;\n    email?: string | null;\n  };\n  return { id: raw.id, login: raw.login, name: raw.name ?? null, email: raw.email ?? null };\n}\n","sourceCodeStart":168,"sourceCodeEnd":196,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/github-auth/device-flow.ts#L168-L196","documentation":"fetchGithubUser calls GET https://api.github.com/user with the access token. Any non-2xx HTTP status is converted into DeviceFlowError('user_fetch_failed') embedding the status code, so callers get one error type for profile fetch failures.","triggerScenarios":"Calling fetchGithubUser(accessToken) when GitHub returns 401 (bad/expired token), 403 (rate limit or forbidden), or 5xx from the /user endpoint.","commonSituations":"Access token expired or revoked; token lacks the right scopes; hitting GitHub API rate limits without backoff; GitHub incident causing 5xx; corporate proxy blocking api.github.com.","solutions":["Refresh or re-obtain the access token via refreshUserToken / device flow","Check the embedded HTTP status: 401 means re-auth, 403 may mean rate limit — wait and retry","Verify the token has required scopes for reading the user profile","Check GitHub status / network connectivity if 5xx persists"],"exampleFix":"// before\nconst user = await fetchGithubUser(accessToken);\n// after\ntry {\n  const user = await fetchGithubUser(accessToken);\n} catch (e) {\n  if (e instanceof DeviceFlowError && e.code === 'user_fetch_failed' && /HTTP 401/.test(e.message)) {\n    accessToken = await refreshAccessToken(clientId, refreshToken);\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"if (!accessToken) throw new Error('No access token: run device flow before fetchGithubUser');","typeGuard":null,"tryCatchPattern":"try { return await fetchGithubUser(token); } catch (e) { if (e instanceof DeviceFlowError && e.code === 'user_fetch_failed') { if (e.message.includes('401')) token = await reauthenticate(); else if (/403|5\\d\\d/.test(e.message)) await backoff(); } else throw e; }","preventionTips":["Refresh expired tokens proactively before calling the API","Respect GitHub rate limits with backoff on 403/429","Verify token scopes cover user profile reads"],"tags":["http","github-api","authentication","network"],"backgroundTag":"http-401-unauthorized","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}