{"record":{"id":"ff92e6c2de4d2e5c","repo":"ToolJet/ToolJet","slug":"query-could-not-be-completed-ff92e6","errorCode":null,"errorMessage":"Query could not be completed","messagePattern":"Query could not be completed","errorType":"exception","errorClass":"OAuthUnauthorizedClientError","httpStatus":401,"severity":"error","filePath":"plugins/packages/googlesheets/lib/index.ts","lineNumber":182,"sourceCode":"        const errorRespose = JSON.parse(error.response.body);\n        errorDetails = {\n          message: errorRespose?.error?.message,\n          status: errorRespose?.error?.status,\n        };\n      }\n\n      if (error.message.replace(/\\s+/g, ' ').trim().includes('Unexpected token in JSON')) {\n        errorDetails = {\n          message: 'Invalid JSON',\n        };\n      }\n      // For OAuth if token is expired or invalid it returns 401 or 403\n      // For other authentication types just throw generic error so that user can re-authenticate\n      if (\n        sourceOptions['authentication_type'] !== 'service_account' &&\n        (error?.response?.statusCode === 401 || error?.response?.statusCode === 403)\n      ) {\n        throw new OAuthUnauthorizedClientError('Query could not be completed', error.message, {\n          ...error,\n          ...errorDetails,\n        });\n      }\n      throw new QueryError('Query could not be completed', error.message, errorDetails);\n    }\n\n    return {\n      status: 'ok',\n      data: result,\n    };\n  }\n\n  async refreshToken(sourceOptions) {\n    if (!sourceOptions['refresh_token']) {\n      throw new QueryError('Query could not be completed', 'Refresh token empty', {});\n    }\n    const accessTokenUrl = 'https://oauth2.googleapis.com/token';","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/plugins/packages/googlesheets/lib/index.ts#L164-L200","documentation":"In the Google Sheets run() catch block, when authentication_type is NOT 'service_account' and the HTTP response status is 401 or 403, the plugin throws OAuthUnauthorizedClientError (a distinct Error subclass signaling the OAuth token is invalid/expired so ToolJet can prompt re-auth) instead of the generic QueryError. This branch exists specifically to tell the platform the user must re-authorize.","triggerScenarios":"OAuth access token expired (Google access tokens last ~1h) and a read/append/update/delete/info/list_all call returned 401. The user revoked ToolJet access in their Google account, producing 401/403. The refresh token is invalid or was never persisted. The OAuth app scopes were changed and the existing grant no longer covers spreadsheets.","commonSituations":"Long-running app sessions past the 1h access-token window where the refresh path failed. User manually revoked the consent. GOOGLE_CLIENT_ID/SECRET env vars changed so refresh fails. App restored from an export where the OAuth grant did not carry over. Migrating between ToolJet instances without re-consent.","solutions":["Re-authorize the Google Sheets datasource through the ToolJet OAuth flow to obtain a fresh access+refresh token.","Ensure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET env vars on the ToolJet server match the Google Cloud OAuth app used for the original consent.","Confirm the OAuth consent screen includes the spreadsheets scope; re-consent if scopes changed.","If the issue is only an expired access token, verify refreshToken() (lines 196+) can exchange the refresh_token successfully."],"exampleFix":"// no code fix — user must re-run the OAuth consent flow in the datasource settings","handlingStrategy":"try-catch","validationCode":"// No pre-call validation can prevent an expired access token; detect at runtime.\n// Proactively refresh before the 1h window: schedule refreshToken() calls.","typeGuard":"function isOAuthUnauthorized(e: unknown): e is OAuthUnauthorizedClientError { return e instanceof Error && e.name === 'OAuthUnauthorizedClientError'; }","tryCatchPattern":"try { await plugin.run(sourceOptions, queryOptions); }\ncatch (e) {\n  if (e instanceof OAuthUnauthorizedClientError) { /* trigger re-consent / refresh flow, then retry once */ }\n  else throw e;\n}","preventionTips":["Ensure access_type=offline and prompt=consent on the original OAuth grant.","Keep GOOGLE_CLIENT_ID/SECRET stable so refresh succeeds.","Refresh the access token before the 1h expiry instead of waiting for a 401."],"tags":["googlesheets","oauth","authentication","expired-token","tooljet"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}