different-ai/openwork · error · ApiError

google_workspace_not_connected

google_workspace_not_connected

Error message

Connect Google Workspace in OpenWork Settings to use this tool.

What it means

googleWorkspaceAccessToken() is the entry point every Google Workspace tool uses to obtain a bearer token. It reads the extension's oauth vault and, if there is no primary account record at all, throws an ApiError with status 400 and code google_workspace_not_connected instead of attempting a refresh. This is the library's signal that the user never completed (or erased) the Google OAuth connect flow.

Source

Thrown at apps/server/src/extensions/google-workspace.ts:627

    });
  if (!isRecord(refreshed) || typeof refreshed.access_token !== "string") throw new Error("Google OAuth refresh did not return an access token.");
  const next = {
    ...record,
    scopes: typeof refreshed.scope === "string" ? refreshed.scope.split(/\s+/).filter(Boolean) : record.scopes,
    token: {
      accessToken: refreshed.access_token,
      refreshToken: typeof refreshed.refresh_token === "string" ? refreshed.refresh_token : refreshToken,
      expiresAt: Date.now() + Number(refreshed.expires_in ?? 3600) * 1000,
    },
    updatedAt: new Date().toISOString(),
  };
  return next;
}

async function googleWorkspaceAccessToken(config: ServerConfig): Promise<{ record: Record<string, unknown>; accessToken: string }> {
  const vault = await readGoogleWorkspaceVault(config);
  const record = googleWorkspacePrimaryRecord(vault);
  if (!record) throw new ApiError(400, "google_workspace_not_connected", "Connect Google Workspace in OpenWork Settings to use this tool.");
  const refreshed = await refreshGoogleWorkspaceVault(record);
  const refreshedAccountId = googleWorkspaceAccountId(refreshed);
  if (refreshedAccountId) {
    const nextAccounts = googleWorkspaceAccountRecords(vault).map((entry) => googleWorkspaceAccountId(entry) === refreshedAccountId ? refreshed : entry);
    await writeGoogleWorkspaceAccountsVault(config, nextAccounts, refreshedAccountId);
  }
  const token = isRecord(refreshed.token) ? refreshed.token : null;
  const accessToken = typeof token?.accessToken === "string" ? token.accessToken : "";
  if (!accessToken) throw new Error("Google Workspace access token is unavailable. Reconnect Google Workspace.");
  return { record: refreshed, accessToken };
}

function multipartRelatedBody(metadata: Record<string, unknown>, content: string, boundary: string): string {
  return [
    `--${boundary}`,
    "Content-Type: application/json; charset=UTF-8",
    "",
    JSON.stringify(metadata),

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Connect Google Workspace in OpenWork Settings (complete the OAuth browser flow) — this populates the vault record
  2. Verify the server's configDir actually contains extensions/google-workspace/oauth.vault for the environment you are calling
  3. If a connection exists but is unreadable, check the vault-key file and file permissions on the extension directory
  4. Check the connect status endpoint first (googleWorkspaceStatusPayload) before invoking tools

Example fix

// before
await callTool("gmail_list_messages", {}) // ApiError 400 google_workspace_not_connected

// after: complete Settings > Connect Google Workspace OAuth flow once
await callTool("gmail_list_messages", {}) // works with stored tokens
Defensive patterns

Strategy: validation

Validate before calling

const status = await getGoogleWorkspaceStatus(); // googleWorkspaceStatusPayload: { configured, connected, ... }
if (!status.connected) {
  return { redirectTo: "/settings/google-workspace" }; // ask user to connect first
}
const { accessToken } = await googleWorkspaceAccessToken(config);

Type guard

function isConnected(vault: unknown): vault is { accounts: Record<string, unknown>[] } {
  return typeof vault === "object" && vault !== null &&
    Array.isArray((vault as { accounts?: unknown }).accounts) &&
    (vault as { accounts: unknown[] }).accounts.length > 0;
}

Try / catch

try {
  const { accessToken } = await googleWorkspaceAccessToken(config);
} catch (err) {
  if (err instanceof ApiError && err.code === "google_workspace_not_connected") {
    ui.showConnectPrompt("Connect Google Workspace in OpenWork Settings to use this tool.");
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Invoking any Google Workspace tool/endpoint (Gmail, Drive, Calendar tools) before any OAuth connect has completed, or after the vault was cleared/failed to decrypt so googleWorkspacePrimaryRecord(vault) returns null.

Common situations: Fresh server install where Settings > Google Workspace connect was never run; vault directory removed or permissions broken so the record cannot be read; connecting in one environment (desktop) and calling tools against another (server) with a different configDir; vault key missing so the encrypted vault is unreadable.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/a28d080d60b2008b. Report an issue: GitHub.