{"record":{"id":"b4418ccce1d9a78d","repo":"hcengineering/platform","slug":"workspace-is-closing","errorCode":null,"errorMessage":"Workspace is closing","messagePattern":"Workspace is closing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"foundations/server/packages/server/src/sessionManager.ts","lineNumber":1557,"sourceCode":"    ws: ConnectionSocket,\n    operation: (ctx: ClientSessionCtx, rateLimit: RateLimitInfo | undefined) => Promise<void>\n  ): Promise<RateLimitInfo | undefined> {\n    const rateLimitStatus = this.checkRate(service)\n    // If remaining is 0, rate limit is exceeded\n    if (rateLimitStatus?.remaining === 0) {\n      return await Promise.resolve(rateLimitStatus)\n    }\n\n    const source = service.token.extra?.service ?? '🤦‍♂️user'\n\n    // Calculate total number of clients\n    const reqId = generateId()\n\n    const st = Date.now()\n    try {\n      const workspace = this.workspaces.get(service.workspace.uuid)\n      if (workspace === undefined || workspace.closing !== undefined) {\n        throw new Error('Workspace is closing')\n      }\n\n      service.requests.set(reqId, {\n        id: reqId,\n        params: {},\n        start: st\n      })\n\n      try {\n        await this.counters.withCounter('request', 1, () =>\n          workspace.with(async (pipeline) => {\n            await requestCtx.with(\n              '🧨 ' + method,\n              { source, mode: 'rpc' },\n              (callTx) =>\n                operation(\n                  this.createOpContext(callTx, requestCtx, pipeline, reqId, service, ws, rateLimitStatus),\n                  rateLimitStatus","sourceCodeStart":1539,"sourceCodeEnd":1575,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/server/packages/server/src/sessionManager.ts#L1539-L1575","documentation":"TSessionManager tracks workspaces and their closing state. When handling a request it looks up the workspace by uuid and refuses to serve if the workspace is missing or is in the middle of closing (workspace.closing !== undefined), throwing 'Workspace is closing' so requests are not processed against a shutting-down workspace.","triggerScenarios":"Sending a request (with a freshly generated reqId) to a workspace that is currently shutting down, or to a workspace uuid that no longer exists in the manager's map.","commonSituations":"Client requests racing server shutdown/workspace close; request sent right after workspace was closed or migrated; reconnect attempts during maintenance windows.","solutions":["Retry the request after the workspace finishes closing/restarting","Check workspace availability (or wait for a 'ready' signal) before sending requests","Re-open/reconnect the workspace if it was permanently closed","Handle this error client-side as a transient shutdown signal and back off"],"exampleFix":"// before\nawait sendRequest(service, params)\n// after\ntry {\n  await sendRequest(service, params)\n} catch (e) {\n  if (e.message === 'Workspace is closing') {\n    await waitForWorkspaceReady(service.workspace.uuid)\n    await sendRequest(service, params)\n  } else throw e\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"async function sendWithRetry(fn, { retries = 3, baseMs = 500 } = {}) {\n  for (let i = 0; ; i++) {\n    try {\n      return await fn()\n    } catch (e) {\n      if (e.message === 'Workspace is closing' && i < retries) {\n        await new Promise(r => setTimeout(r, baseMs * 2 ** i))\n        continue\n      }\n      throw e\n    }\n  }\n}","preventionTips":["Track workspace lifecycle state client-side and pause sends while a close/restart is in flight","Subscribe to workspace availability events instead of polling blind","Use exponential backoff on this specific error; it is transient during shutdowns","During maintenance, drain clients before closing workspaces"],"tags":["workspace","shutdown","session"],"backgroundTag":"workspace-closing","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}