{"record":{"id":"93bdd952804e4c9c","repo":"different-ai/openwork","slug":"missing-google-oauth-configuration-missing-join","errorCode":null,"errorMessage":"Missing Google OAuth configuration: ${missing.join(\", \")}","messagePattern":"Missing Google OAuth configuration: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/server/src/extensions/google-workspace.ts","lineNumber":569,"sourceCode":"            ? payload.error\n            : response.statusText\n      : response.statusText;\n    throw new Error(`Google request failed (${response.status}): ${details}`);\n  }\n  return payload;\n}\n\nasync function fetchGoogleUserInfo(accessToken: string) {\n  return fetchGoogleJson(\"https://www.googleapis.com/oauth2/v3/userinfo\", { headers: { Authorization: `Bearer ${accessToken}` } });\n}\n\nasync function fetchGoogleWorkspaceTokenBrokerJson(tokenBrokerUrl: string, body: Record<string, unknown>) {\n  return fetchGoogleJson(tokenBrokerUrl, { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify(body) });\n}\n\nasync function exchangeGoogleWorkspaceCode(input: { code: string; redirectUri: string; verifier: string }) {\n  const { clientId, clientSecret, tokenBrokerUrl, missing } = googleWorkspaceCredentials();\n  if (missing.length > 0) throw new Error(`Missing Google OAuth configuration: ${missing.join(\", \")}`);\n  if (tokenBrokerUrl) {\n    return fetchGoogleWorkspaceTokenBrokerJson(tokenBrokerUrl, {\n      grantType: \"authorization_code\",\n      provider: GOOGLE_WORKSPACE_EXTENSION_ID,\n      clientId,\n      code: input.code,\n      codeVerifier: input.verifier,\n      redirectUri: input.redirectUri,\n    });\n  }\n  return fetchGoogleJson(\"https://oauth2.googleapis.com/token\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n    body: new URLSearchParams({\n      client_id: clientId,\n      client_secret: clientSecret,\n      code: input.code,\n      code_verifier: input.verifier,","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/extensions/google-workspace.ts#L551-L587","documentation":"The Google Workspace extension throws this when resolving OAuth credentials via googleWorkspaceCredentials() before exchanging an authorization code for tokens. The helper reads GOOGLE_WORKSPACE_CLIENT_ID / GOOGLE_WORKSPACE_CLIENT_SECRET (with legacy fallbacks) from the environment; if the client secret is absent and no token broker URL is configured, the missing list is non-empty and the exchange is refused. The bundled desktop client ID is used by default, so this is almost always a missing/empty secret or a secret env var typo when running a custom client.","triggerScenarios":"Calling the OAuth connect flow (exchangeGoogleWorkspaceCode with a valid code/redirectUri/verifier) when googleWorkspaceCredentials().missing is non-empty — i.e. neither GOOGLE_WORKSPACE_CLIENT_SECRET nor its legacy variant is set AND GOOGLE_WORKSPACE_TOKEN_BROKER_URL is unset.","commonSituations":"Self-hosting the OpenWork server and forgetting to export the client secret env var; typos or empty-string env values (whitespace is trimmed, so ' ' counts as missing); migrating deployments where the legacy secret env var name was renamed; pointing the client at a custom OAuth client ID without providing its secret.","solutions":["Set the GOOGLE_WORKSPACE_CLIENT_SECRET environment variable (or the legacy variant it falls back to) to your OAuth client secret and restart the server","Alternatively set GOOGLE_WORKSPACE_TOKEN_BROKER_URL so tokens are exchanged via the token broker and no secret is needed locally","Check the exact env var names against googleWorkspaceCredentials() in apps/server/src/extensions/google-workspace.ts:321 — whitespace-only values are treated as missing","Call the connect status endpoint (googleWorkspaceStatusPayload reports `configured`/`missing`) before starting a connect flow"],"exampleFix":"// before\nGOOGLE_WORKSPACE_CLIENT_ID=my-client.apps.googleusercontent.com\ngoogle oauth connect ...\n// Error: Missing Google OAuth configuration: GOOGLE_WORKSPACE_CLIENT_SECRET\n\n// after\nGOOGLE_WORKSPACE_CLIENT_ID=my-client.apps.googleusercontent.com\nGOOGLE_WORKSPACE_CLIENT_SECRET=GOCSPX-xxxx\ngoogle oauth connect ... // succeeds","handlingStrategy":"validation","validationCode":"const credentials = googleWorkspaceCredentials();\nif (credentials.missing.length > 0) {\n  throw new Error(`Configure before connecting Google Workspace: missing ${credentials.missing.join(\", \")}`);\n}\n// or without internals:\nif (!process.env.GOOGLE_WORKSPACE_CLIENT_SECRET && !process.env.GOOGLE_WORKSPACE_TOKEN_BROKER_URL) {\n  throw new Error(\"Set GOOGLE_WORKSPACE_CLIENT_SECRET or GOOGLE_WORKSPACE_TOKEN_BROKER_URL first\");\n}","typeGuard":"function isGoogleOauthConfigured(): boolean {\n  return Boolean(\n    process.env.GOOGLE_WORKSPACE_CLIENT_SECRET?.trim() ||\n    process.env.GOOGLE_WORKSPACE_TOKEN_BROKER_URL?.trim(),\n  );\n}","tryCatchPattern":"try {\n  await startGoogleWorkspaceConnect();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Missing Google OAuth configuration\")) {\n    showSetupGuide(err.message.split(\": \")[1]?.split(\", \") ?? []);\n    return;\n  }\n  throw err;\n}","preventionTips":["Always check the connect/status endpoint (reports configured + missing vars) before starting a connect flow","Keep OAuth env vars in a versioned env template so deployments get all required names","Remember empty/whitespace values count as missing — validate env files after edits","When using a custom client ID, provision the secret in the same change"],"tags":["oauth","configuration","environment","google-workspace"],"backgroundTag":"missing-env-var","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}