{"record":{"id":"bdaf8aa9f8edfbc5","repo":"decolua/9router","slug":"failed-to-fetch-project-id-error","errorCode":null,"errorMessage":"Failed to fetch project ID: ${error}","messagePattern":"Failed to fetch project ID: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/gemini.js","lineNumber":85,"sourceCode":"      {\n        method: \"POST\",\n        headers: {\n          \"Authorization\": `Bearer ${accessToken}`,\n          \"Content-Type\": \"application/json\",\n          \"User-Agent\": \"google-api-nodejs-client/9.15.1\",\n          \"X-Goog-Api-Client\": \"google-cloud-sdk vscode_cloudshelleditor/0.1\",\n          \"Client-Metadata\": JSON.stringify(getOAuthClientMetadata())\n        },\n        body: JSON.stringify({\n          metadata: getOAuthClientMetadata(),\n          mode: 1\n        })\n      }\n    );\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Failed to fetch project ID: ${error}`);\n    }\n\n    const data = await response.json();\n\n    // Extract project ID\n    let projectId = \"\";\n    if (typeof data.cloudaicompanionProject === \"string\") {\n      projectId = data.cloudaicompanionProject.trim();\n    } else if (data.cloudaicompanionProject?.id) {\n      projectId = data.cloudaicompanionProject.id.trim();\n    }\n\n    if (!projectId) {\n      throw new Error(\"No project ID found in response\");\n    }\n\n    return projectId;\n  }","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/gemini.js#L67-L103","documentation":"GeminiCLIService.fetchProjectId() (src/lib/oauth/services/gemini.js:85) calls Google's Cloud Code Assist endpoint (cloudcode-pa.googleapis.com/v1internal:loadCodeAssist) with the fresh access token to discover the user's cloudaicompanionProject. A non-2xx response is wrapped into 'Failed to fetch project ID: <body>' and thrown, mid-way through connect() after tokens were already obtained.","triggerScenarios":"Calling fetchProjectId(accessToken) when Google replies non-2xx — 401/403 when the token lacks the cloud-platform scope or the account has no Code Assist entitlement, 404 when the internal API shape/URL changed, 429 on quota, or 5xx from Google.","commonSituations":"The Google account is a Workspace account without Cloud Code Assist enabled; the user denied the requested scopes on the consent screen; Google changed the v1internal API contract (common for internal endpoints); transient Google-side outage right after login.","solutions":["Read the embedded body for the status code and Google error details (401/403 vs 429 vs 5xx).","For 401/403: re-run connect(), approve ALL requested scopes on the consent screen, and verify the account has Gemini Code Assist available.","For 429: wait and retry — the call is rate-limited per account.","For 5xx: retry after a short backoff; if it persists, check whether Google changed the loadCodeAssist endpoint and update the URL/metadata in fetchProjectId.","Confirm system clock correctness — a skewed clock invalidates bearer tokens right after exchange."],"exampleFix":"// before\nif (!response.ok) {\n  const error = await response.text();\n  throw new Error(`Failed to fetch project ID: ${error}`);\n}\n// after\nif (!response.ok) {\n  const error = await response.text();\n  if (response.status >= 500 || response.status === 429) {\n    await new Promise((r) => setTimeout(r, 2000));\n    return this.fetchProjectId(accessToken); // transient — retry once\n  }\n  throw new Error(`Failed to fetch project ID (HTTP ${response.status}): ${error}`);\n}","handlingStrategy":"retry","validationCode":"// pre-checks before the loadCodeAssist call\nif (!accessToken) throw new Error(\"Access token required for loadCodeAssist\");\n// optionally verify token works first via getUserInfo(), which fails faster on 401","typeGuard":"function isTransientHttpError(status) {\n  return status === 429 || status >= 500;\n}","tryCatchPattern":"try {\n  const projectId = await geminiService.fetchProjectId(tokens.access_token);\n} catch (err) {\n  const m = /Failed to fetch project ID: (.*)/s.exec(err.message);\n  if (m && /401|403/.test(m[1])) {\n    console.error(\"Account lacks Code Assist access or scopes were not granted — re-run connect() and approve all scopes.\");\n  } else if (m && /429|5\\d\\d/.test(m[1])) {\n    console.error(\"Transient Google error — retry after backoff.\");\n  } else throw err;\n}","preventionTips":["Use a Google account with Gemini Code Assist enabled/onboarded.","Approve all requested scopes on the consent screen; missing cloud-platform scope breaks this call.","Keep the system clock accurate — skewed clocks invalidate fresh bearer tokens.","Wrap project-ID fetch with bounded backoff retries for 429/5xx.","Watch for Google changing the v1internal endpoint and pin a fallback."],"tags":["network","gemini","google-cloud","http-error","project-id"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}