{"record":{"id":"d62341873c0068b1","repo":"hcengineering/platform","slug":"session-hang-closing","errorCode":null,"errorMessage":"session hang, closing...","messagePattern":"session hang, closing\\.\\.\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"foundations/server/packages/server/src/sessionManager.ts","lineNumber":375,"sourceCode":"    this.ctx.measure('workspaces', this.workspaces.size, true)\n    this.ctx.measure('workspaces-user', userWorkspaces, true)\n    this.ctx.measure('workspaces-systemonly', sysOnlyWorkspaces, true)\n  }\n\n  private handleSessionTick (now: number): void {\n    for (const s of this.sessions.values()) {\n      const isCurrentUserTick = this.ticks % ticksPerSecond === s.tickHash\n\n      if (isCurrentUserTick) {\n        const wsId = s.session.workspace.uuid\n        const lastRequestDiff = now - s.session.lastRequest\n\n        let timeout = hangSessionTimeoutSeconds * 1000\n        if (s.session.getUser() === systemAccountUuid) {\n          timeout = timeout * 10\n        }\n        if (lastRequestDiff > timeout) {\n          this.ctx.warn('session hang, closing...', {\n            wsId,\n            user: s.session.getUser()\n          })\n\n          // Force close workspace if only one client and it hang.\n          void this.close(this.ticksContext, s.socket, wsId).catch((err) => {\n            this.ctx.error('failed to close', err)\n          })\n          continue\n        }\n        if (\n          lastRequestDiff + (1 / 10) * lastRequestDiff > this.timeouts.pingTimeout &&\n          now - s.session.lastPing > this.timeouts.pingTimeout\n        ) {\n          // We need to check state and close socket if it broken\n          // And ping other wize\n          s.session.lastPing = now\n          if (s.socket.checkState()) {","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/server/packages/server/src/sessionManager.ts#L357-L393","documentation":"Session tick detects a session whose last request is older than hangSessionTimeoutSeconds (x10 for the system account) and logs this warning before force-closing the socket. A hung session holds a workspace open and consumes resources; the manager proactively closes it.","triggerScenarios":"handleSessionTick finds lastRequestDiff > timeout for a session — the client stopped sending requests/pings without a clean disconnect (dead network connection, paused/tab-suspended client, crashed process without TCP close).","commonSituations":"Mobile or laptop clients going to sleep; NAT/firewall silently dropping idle sockets; client bugs stopping the ping loop; system-account sessions with very long idle times.","solutions":["Ensure clients send periodic ping/heartbeat to keep the session alive","Check client network paths (NAT idle timeouts) and lower keepalive intervals","If legitimate long-idle system sessions get killed, raise hangSessionTimeoutSeconds","Investigate client-side crash/freeze logs if the same users repeatedly hang"],"exampleFix":"// before\nclient ping interval: none (server closes after timeout)\n// after\nsetInterval(() => socket.ping(), 30000) // < hangSessionTimeoutSeconds","handlingStrategy":"validation","validationCode":"const lastPing = Date.now() - lastRequestTs\nif (lastPing > hangSessionTimeoutSeconds * 1000) {\n  await forceReconnect() // reconnect before the server closes the session\n}","typeGuard":"function isSessionHung(lastRequestTs: number, timeoutSec: number, isSystem = false): boolean {\n  return Date.now() - lastRequestTs > timeoutSec * 1000 * (isSystem ? 10 : 1)\n}","tryCatchPattern":"socket.on('close', () => {\n  scheduleReconnectWithBackoff()\n})\n// server-side force close surfaces as socket close — client should reconnect transparently","preventionTips":["Send pings from clients at an interval well under hangSessionTimeoutSeconds","Enable TCP keepalive on client sockets","Handle reconnect on socket close with exponential backoff","Give system-account sessions explicit longer timeouts in client tooling"],"tags":["session","timeout","websocket"],"backgroundTag":"hung-session-closed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}