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
- Connect Google Workspace in OpenWork Settings (complete the OAuth browser flow) — this populates the vault record
- Verify the server's configDir actually contains extensions/google-workspace/oauth.vault for the environment you are calling
- If a connection exists but is unreadable, check the vault-key file and file permissions on the extension directory
- 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
- Check the extension status payload (connected/configured) before enabling Google Workspace tools in the UI
- Gate tool availability on connection state so users never hit the 400 mid-workflow
- Remember configDir is per-environment: a connection made in desktop dev doesn't carry to a server deployment
- Verify vault + vault-key files exist and are readable if a known-connected account suddenly reports not connected
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
- Missing Google OAuth configuration: ${missing.join(", ")}
- Google Workspace refresh token is missing. Reconnect Google
- Google OAuth refresh did not return an access token.
- `${t("providers.no_oauth_prefix")} ${resolved}. ${t("provide
- `${t("providers.not_oauth_flow_prefix")} ${resolved}.`
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/a28d080d60b2008b.
Report an issue: GitHub.