hcengineering/platform · error

DB client is already closed

Error message

DB client is already closed

What it means

getClient on the postgres-base connection wrapper throws this Error when the connection has already been closed (`closed === true`). After close() is called, the underlying postgres.Sql client is no longer usable, so any attempt to obtain a client is rejected. It guards against using or double-closing a shut-down DB connection.

Source

Thrown at foundations/core/packages/postgres-base/src/index.ts:166

export class ClientRef implements PostgresClientReference {
  id = ++clId
  constructor (
    readonly client: PostgresClientReferenceImpl,
    readonly mgr: ConnectionMgr
  ) {
    clientRefs.set(this.id, this)
  }

  url (): string {
    return this.client.url()
  }

  closed = false
  async getClient (): Promise<postgres.Sql> {
    if (!this.closed) {
      return this.client.getClient()
    } else {
      throw Error('DB client is already closed')
    }
  }

  close (): void {
    // Do not allow double close of mongo connection client
    if (!this.closed) {
      clientRefs.delete(this.id)
      this.closed = true
      this.client.close()
    }
  }
}

export let dbExtraOptions: Partial<Options<any>> = {}
export function setDBExtraOptions (options: Partial<Options<any>>): void {
  dbExtraOptions = options
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Guard with the public `closed` property before calling getClient, and re-create the connection if it was closed
  2. Do not call close() until all pending operations complete; await outstanding work before teardown
  3. Ensure singleton connection holders are updated (set to null/recreated) after close so stale references aren't reused
  4. In tests, use a fresh connection per test or only close after all awaited queries finish

Example fix

// before
const client = await conn.getClient() // throws if closed
// after
if (conn.closed) {
  conn = createConnection(config)
}
const client = await conn.getClient()
Defensive patterns

Strategy: type-guard

Validate before calling

if (conn.closed) {
  conn = recreateConnection(config)
}
const client = await conn.getClient()

Type guard

function isConnectionOpen(conn: { closed: boolean }): boolean {
  return !conn.closed
}

Try / catch

let client: postgres.Sql
try {
  client = await conn.getClient()
} catch (e) {
  if (e instanceof Error && e.message === 'DB client is already closed') {
    conn = recreateConnection(config)
    client = await conn.getClient()
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Calling getClient (foundations/core/packages/postgres-base/src/index.ts:166) on a connection instance after close() has been invoked on it — e.g., during shutdown handlers, hot-reload, or when a cached connection reference outlives its close call.

Common situations: Application shutdown code closing the DB while in-flight requests still call getClient; tests that close a shared connection in afterEach while other async work continues; service restart/reconnect logic holding a stale singleton; double teardown from both framework lifecycle and explicit close.

Related errors


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