{"record":{"id":"31095f11f2cec5ef","repo":"mastra-ai/mastra","slug":"google-directory-groups-list-failed-response-st","errorCode":null,"errorMessage":"Google Directory groups.list failed (${response.status}): ${await response.text()}","messagePattern":"Google Directory groups\\.list failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"auth/google/src/rbac-provider.ts","lineNumber":158,"sourceCode":"\n    do {\n      const url = new URL(DIRECTORY_GROUPS_URL);\n      url.searchParams.set('userKey', userKey);\n      url.searchParams.set('maxResults', '200');\n      if (pageToken) {\n        url.searchParams.set('pageToken', pageToken);\n      }\n\n      const response = await fetch(url, {\n        headers: {\n          Authorization: `Bearer ${token}`,\n          Accept: 'application/json',\n        },\n        signal: AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT_MS),\n      });\n\n      if (!response.ok) {\n        throw new Error(`Google Directory groups.list failed (${response.status}): ${await response.text()}`);\n      }\n\n      const json = (await response.json()) as GroupsListResponse;\n      for (const group of json.groups ?? []) {\n        const mappedRoles = this.options.mapGroupToRoles?.(group) ?? [group.email];\n        for (const role of mappedRoles) {\n          if (role) roles.add(role);\n        }\n      }\n      pageToken = json.nextPageToken;\n    } while (pageToken);\n\n    return Array.from(roles);\n  }\n\n  private async getToken(): Promise<string> {\n    if (this.options.getAccessToken) {\n      return this.options.getAccessToken();","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/auth/google/src/rbac-provider.ts#L140-L176","documentation":"Thrown by MastraRBACGoogle.fetchRolesFromGoogle when the Admin SDK Directory API groups.list call (https://admin.googleapis.com/admin/directory/v1/groups) returns a non-OK status. The HTTP status and raw response body are embedded in the message. It means the bearer token was rejected or the Directory API refused the query (permissions, domain, userKey problems).","triggerScenarios":"getRoles(user) (via rolesPromise) hits the Directory API and receives 401 (expired/invalid token), 403 (service account lacks domain-wide delegation, missing admin.directory.group.readonly scope, or API not enabled), 404 (userKey not found in domain), or 400 (bad userKey/email).","commonSituations":"Service account without domain-wide delegation or the 'sub' (subject) not set to an admin user; Admin SDK API disabled in Google Cloud project; access token from a different audience/scope; querying a user email outside the Workspace domain; 10s AbortSignal timeout resulting in network failures surfaced separately.","solutions":["Read the HTTP status in the message: 401 => refresh/fix the token; 403 => fix delegation/scopes; 404/400 => check userKey.","For service accounts, enable domain-wide delegation in Workspace Admin and set subject to an admin email with group read rights.","Ensure the token scope includes https://www.googleapis.com/auth/admin.directory.group.readonly and the Admin SDK API is enabled in the Google Cloud project.","Verify the user's email (or getUserKey result) belongs to the Workspace domain and is a valid userKey.","Confirm the Admin SDK Directory API is enabled for the project in Google Cloud Console.","Handle transient 5xx with a retry; fetchRolesFromGoogle errors are logged and rethrown after cache eviction."],"exampleFix":"// before (no subject => service account has no delegated identity)\nconst rbac = new MastraRBACGoogle({ serviceAccount: { clientEmail, privateKey } });\n// after\nconst rbac = new MastraRBACGoogle({\n  serviceAccount: {\n    clientEmail,\n    privateKey,\n    subject: 'admin@mycompany.com', // domain-wide delegation target\n    scopes: ['https://www.googleapis.com/auth/admin.directory.group.readonly'],\n  },\n  roleMapping: { _default: [] },\n});","handlingStrategy":"retry","validationCode":"// before calling getRoles, confirm a usable Directory token exists and the user key is plausible\nif (!user.email || !user.email.includes('@')) return []; // skip Directory lookup for invalid keys\n// ensure API access once at startup:\n// admin.googleapis.com Admin SDK enabled + token scope includes admin.directory.group.readonly","typeGuard":"function isDirectoryApiError(err: unknown): err is Error & { message: string } {\n  return err instanceof Error && err.message.startsWith('Google Directory groups.list failed (');\n}\nfunction directoryStatus(err: Error & { message: string }): number | null {\n  const m = /groups\\.list failed \\((\\d{3})\\)/.exec(err.message);\n  return m ? Number(m[1]) : null;\n}","tryCatchPattern":"try {\n  const roles = await rbac.getRoles(user);\n} catch (err) {\n  if (isDirectoryApiError(err)) {\n    const status = directoryStatus(err);\n    if (status === 429 || (status && status >= 500)) {\n      await retryWithBackoff(() => rbac.getRoles(user)); // transient — safe to retry\n    } else {\n      // 401/403: fix token/delegation/scopes; 404/400: bad userKey — do not blind-retry\n      return fallbackRoles; // e.g. roleMapping['_default']\n    }\n  } else throw err;\n}","preventionTips":["Enable domain-wide delegation for the service account and set subject to an admin email","Include admin.directory.group.readonly in the token scopes and enable the Admin SDK API","Prefer serviceAccount auth so tokens are auto-refreshed instead of static access tokens","Degrade gracefully: fall back to roleMapping._default when Directory lookups fail","Keep user emails normalized/validated before passing them as userKey"],"tags":["google","network","api-error","rbac","directory-api"],"backgroundTag":"api-request-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}