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
- Expected on admin teardown — clients will receive code 99 and reconnect, hitting the 'connect during upgrade' path until maintenance is cleared.
- Verify clients reconnect successfully afterwards; if they loop, check whether maintenanceWorkspaces/workspaceInfoCache state is stale.
- Investigate why the force-close was needed (stuck sessions, hung requests) using the statistics/health endpoints.
- 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
- Only call forceClose as a last-resort admin action after normal close paths fail.
- Track why force-close is needed per workspace; repeated force-closes indicate a systemic bug.
- Pass ignoreSocket when the caller's own connection must survive the teardown.
- Verify clients handle close code 99 and the TxModelUpgrade notification so they refresh and reconnect.
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
- closing workspace, no users
- open workspace
- force-close-unknown
- Clients disconnected. Closing Workspace...
- Workspace closed...
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/b3e0ee80004c77a5.
Report an issue: GitHub.