{"record":{"id":"7c0caa2d1ad006d7","repo":"Budibase/budibase","slug":"koa-context-must-be-supplied-to-logout","errorCode":null,"errorMessage":"Koa context must be supplied to logout.","messagePattern":"Koa context must be supplied to logout\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/auth/auth.ts","lineNumber":191,"sourceCode":"    }\n\n    await db.put(dbUser)\n\n    await invalidateUser(userId)\n  } catch (e) {\n    console.error(\"Could not update OAuth details for current user\", e)\n  }\n}\n\n/**\n * Logs a user out from budibase. Re-used across account portal and builder.\n */\nexport async function platformLogout(opts: PlatformLogoutOpts) {\n  const ctx = opts.ctx\n  const userId = opts.userId\n  const keepActiveSession = opts.keepActiveSession\n\n  if (!ctx) throw new Error(\"Koa context must be supplied to logout.\")\n\n  const currentSession = getCookie<SessionCookie>(ctx, Cookie.Auth)\n  let sessions = await getSessionsForUser(userId)\n\n  if (currentSession && keepActiveSession) {\n    sessions = sessions.filter(\n      session => session.sessionId !== currentSession.sessionId\n    )\n  } else {\n    // clear cookies\n    clearCookie(ctx, Cookie.Auth)\n  }\n\n  const sessionIds = sessions.map(({ sessionId }) => sessionId)\n  await invalidateSessions(userId, { sessionIds, reason: \"logout\" })\n  await events.auth.logout(ctx.user?.email)\n  await userCache.invalidateUser(userId)\n}","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/auth/auth.ts#L173-L209","documentation":"Same failure mode as errorIndex 3 but raised at connection.ts:686 inside fetchSharePointListDocument's item-paging loop: GET /sites/{siteId}/lists/{listId}/items?$expand=fields (or a nextLink page) returned a non-ok status other than 401/403, after the built-in retry pass.","triggerScenarios":"The list items endpoint returns 404 (list deleted), 400 (bad query/listId), 429 (throttled past retries), or 5xx while streaming rows into the CSV.","commonSituations":"List deleted or moved to recycle bin after configuration; list exceeding Graph throttling limits during large syncs; listId copied from a non-Graph source; intermittent Graph 5xx.","solutions":["Check the status in the message: 404 → re-select the list (it no longer exists at that ID).","For 429, reschedule the sync; Graph throttling on large lists is common — reduce $top or sync off-peak.","For 400, verify siteId/listId are raw Graph resource IDs.","Retry after confirming Graph service health; transient 5xx are retried 3 times already."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Confirm list still exists before streaming items\nconst list = await graph(`/sites/${siteId}/lists/${listId}?$select=id`)\nif (!list?.id) throw new Error(\"SharePoint list no longer exists\")","typeGuard":"const isTransientGraphError = (e: unknown): boolean => {\n  const m = /\\((\\d+)\\)\\s*$/.exec(String((e as Error)?.message))\n  return m ? [\"429\", \"500\", \"502\", \"503\", \"504\"].includes(m[1]) : false\n}","tryCatchPattern":"try {\n  await fetchSharePointListDocument(token, siteId, listId)\n} catch (e) {\n  if (isTransientGraphError(e)) {\n    await new Promise(r => setTimeout(r, 30_000))\n    return fetchSharePointListDocument(token, siteId, listId)\n  }\n  throw e\n}","preventionTips":["Schedule large list syncs off-peak to reduce 429 throttling.","Re-verify list existence at the start of each sync run.","Wrap syncs in your own retry layer for 429/5xx beyond the built-in 3 attempts.","Keep raw Graph list IDs; never pass URLs as IDs."],"tags":["sharepoint","microsoft-graph","http","throttling"],"backgroundTag":"graph-api-request-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}