{"record":{"id":"e5400d39ddae6220","repo":"decolua/9router","slug":"projectid-could-not-fetch-projectid-for-connecti","errorCode":null,"errorMessage":"[ProjectId] could not fetch projectId for connection","messagePattern":"\\[ProjectId\\] could not fetch projectId for connection","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"open-sse/services/projectId.js","lineNumber":110,"sourceCode":"        return cached.projectId;\n    }\n\n    // Deduplicate concurrent fetches for the same connection\n    if (pendingFetches.has(connectionId)) {\n        return pendingFetches.get(connectionId).promise;\n    }\n\n    // Each fetch gets its own AbortController so it can be canceled via removeConnection()\n    const controller = new AbortController();\n\n    const promise = (async () => {\n        try {\n            const projectId = await fetchProjectId(accessToken, controller.signal, provider);\n            if (projectId) {\n                projectIdCache.set(connectionId, {projectId, fetchedAt: Date.now()});\n                return projectId;\n            }\n            console.warn(\"[ProjectId] could not fetch projectId for connection\", connectionId.slice(0, 8));\n            return null;\n        } catch (error) {\n            console.warn(`[ProjectId] Error fetching project ID: ${error.message}`);\n            return null;\n        } finally {\n            pendingFetches.delete(connectionId);\n        }\n    })();\n\n    pendingFetches.set(connectionId, {promise, controller, startedAt: Date.now()});\n    return promise;\n}\n\n/**\n * Invalidate the cached project ID for a connection.\n * Call this when a connection's credentials are fully revoked or refreshed.\n */\nexport function invalidateProjectId(connectionId) {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/services/projectId.js#L92-L128","documentation":"getProjectIdForConnection fetches a Google/Vertex-style projectId for an OAuth connection via fetchProjectId. When the fetch succeeds but returns no projectId, it logs this warning, caches nothing, and returns null so callers treat the connection as having no project ID.","triggerScenarios":"fetchProjectId returned falsy — the upstream project-listing endpoint returned 200 but with no matching project, the access token lacks permission to list projects, or the provider variant doesn't expose project IDs.","commonSituations":"OAuth account has no active GCP project; token scopes missing project read permission; provider changed its project discovery API shape so the parser finds nothing; connection created before project setup completed on the provider console.","solutions":["Log into the provider console and confirm the account has at least one active project","Re-run the OAuth flow with scopes including project read access","Check fetchProjectId's parsing against the provider's current API response shape","Retry after the account/project is provisioned — cache refresh (_refreshProjectId) will pick it up","If the provider genuinely needs no projectId, treat the null return as expected and skip project-scoped features"],"exampleFix":"// before: connection created with minimal scopes\nscopes: [\"userinfo.email\"]\n// after: include project read scope\nscopes: [\"userinfo.email\", \"cloud-platform\"]","handlingStrategy":"type-guard","validationCode":"const pid = await getProjectIdForConnection(connectionId);\nif (!pid) console.warn('no projectId — skip project-scoped calls');","typeGuard":"const hasProjectId = (pid) => typeof pid === 'string' && pid.length > 0;\nif (!hasProjectId(projectId)) return; // skip project-scoped feature","tryCatchPattern":"try {\n  const pid = await getProjectIdForConnection(conn.id);\n  if (!pid) return null; // function already returns null on failure, never throws\n  return await projectScopedCall(conn, pid);\n} catch (e) {\n  console.warn(`project-scoped call failed: ${e.message}`);\n  return null;\n}","preventionTips":["Grant project-read scopes during OAuth consent","Provision at least one active project on the provider account before connecting","Treat null projectId as a normal outcome for accounts without projects","Re-run OAuth if the provider changes its project discovery API"],"tags":["oauth","project-id","google","null-return"],"backgroundTag":"oauth-scope-insufficient","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}