hcengineering/platform · warning

force-close

Error message

force-close

What it means

Warning emitted by TSessionManager.forceClose when a workspace is found for the given wsId and is being force-closed: the workspace is marked maintenance (to simulate an upgrade so all clients refresh), doCloseAll is started with close code 99 and reason 'force-close', and the workspace is removed from this.workspaces. This is an administrative teardown, typically of a stuck or misbehaving workspace.

Source

Thrown at foundations/server/packages/server/src/sessionManager.ts:1184

    }
  }

  async forceMaintenance (ctx: MeasureContext, workspaceId: WorkspaceUuid): Promise<void> {
    const workspace = this.workspaces.get(workspaceId)
    this.maintenanceWorkspaces.add(workspaceId)
    if (workspace !== undefined) {
      workspace.maintenance = true
      ctx.info('force-maintenance', { workspaceId })
      await this.doCloseAll(workspace, 99, 'force-close')
    }
  }

  async forceClose (wsId: WorkspaceUuid, ignoreSocket?: ConnectionSocket): Promise<void> {
    const ws = this.workspaces.get(wsId)
    this.maintenanceWorkspaces.delete(wsId)
    this.workspaceInfoCache.delete(wsId)
    if (ws !== undefined) {
      this.ctx.warn('force-close', { name: ws.wsId.url })
      ws.maintenance = true // We need to similare upgrade to refresh all clients.
      ws.closing = this.doCloseAll(ws, 99, 'force-close', ignoreSocket)
      this.workspaces.delete(wsId)
      await ws.closing
      ws.closing = undefined
    } else {
      this.ctx.warn('force-close-unknown', { wsId })
    }
  }

  async doCloseAll (
    workspace: Workspace,
    code: number,
    reason: 'upgrade' | 'shutdown' | 'force-close',
    ignoreSocket?: ConnectionSocket
  ): Promise<void> {
    if (LOGGING_ENABLED) {
      this.ctx.info('closing workspace', {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Expected on admin teardown — clients will receive code 99 and reconnect, hitting the 'connect during upgrade' path until maintenance is cleared.
  2. Verify clients reconnect successfully afterwards; if they loop, check whether maintenanceWorkspaces/workspaceInfoCache state is stale.
  3. Investigate why the force-close was needed (stuck sessions, hung requests) using the statistics/health endpoints.
  4. If forceClose was called unintentionally, audit the admin tooling/automation that invoked it.
Defensive patterns

Strategy: try-catch

Validate before calling

const ws = sessionManager.workspaces.get(wsId)
if (ws === undefined) console.warn('forceClose: nothing to close on this node')

Type guard

function isOpen(ws: Workspace | undefined): ws is Workspace {
  return ws !== undefined && ws.closing === undefined
}

Try / catch

try {
  await sessionManager.forceClose(wsId, currentSocket)
} catch (err) {
  logger.error('force-close failed', { wsId, err })
} finally {
  await verifyWorkspaceRebuildable(wsId) // ensure clients can reconnect afterwards
}

Prevention

When it happens

Trigger: forceClose(wsId, ignoreSocket?) called (e.g., by admin tooling or the workspace service) while this.workspaces.get(wsId) !== undefined. All sessions except ignoreSocket get an upgrade notification then a close(99, 'force-close').

Common situations: Operator force-closing a wedged workspace; workspace service instructing a node to drop a workspace after a failed upgrade; cleaning up before a workspace restart or migration.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/b3e0ee80004c77a5. Report an issue: GitHub.