neoclide/coc.nvim · error · Error

Client is not running and can't be stopped. Its current stat

Error message

Client is not running and can't be stopped. Its current state is: ${this.$state}

What it means

LanguageClient.stop() throws this when the client has no active connection or its state is neither Running nor StartFailed. Clients mid-start, already stopped/disposed, or stopping cannot be stopped again — the state machine forbids it to keep async shutdown synchronized.

Source

Thrown at src/language-client/client.ts:1351

  protected async shutdown(mode: ShutdownMode, timeout: number): Promise<void> {
    // If the client is stopped or in its initial state return.
    if (this.$state === ClientState.Stopped || this.$state === ClientState.Initial) {
      return
    }
    if (this.$state === ClientState.Starting && this._onStart) {
      await this._onStart
    }
    // If we are stopping the client and have a stop promise return it.
    if (this.$state === ClientState.Stopping) {
      return this._onStop
    }

    const connection = this._connection
    // We can't stop a client that is not running (e.g. has no connection). Especially not
    // on that us starting since it can't be correctly synchronized.
    if (connection === undefined || (this.$state !== ClientState.Running && this.$state !== ClientState.StartFailed)) {
      throw new Error(`Client is not running and can't be stopped. Its current state is: ${this.$state}`)
    }
    this._initializeResult = undefined
    this.$state = ClientState.Stopping
    this.cleanUp(mode)

    let tm: NodeJS.Timeout
    const tp = new Promise<any>(c => { tm = setTimeout(c, timeout) })
    const shutdown = (async connection => {
      await connection.shutdown()
      await connection.exit()
      return connection
    })(connection)
    // If the connection closes while the shutdown is in flight (e.g. the
    // server crashed), handleConnectionClosed signals it here so the stop
    // completes successfully: the server is gone, which is the outcome the
    // caller asked for. Without this, the pending shutdown rejects after the
    // connection is disposed, reporting a false stop failure.
    const close = new Promise<Connection>(resolve => {

View on GitHub (pinned to 50e974d969)

Solutions

  1. Guard stop() with client state — only call it when the client is running (or track a started flag in your code).
  2. Await start() completion before allowing stop paths to run; cancel pending starts first.
  3. Make stop idempotent in your code (catch and ignore this error).
  4. If the state is permanently wedged, use :CocRestart or rebuild the client instance.

Example fix

// before
client.stop() // may throw if never started

// after
try {
  await client.stop()
} catch (e) {
  // ignore: client not running (never started or already stopped)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// only stop clients you previously started successfully
let started = false
const start = async () => { await client.start(); started = true }
const stop = async () => { if (started) await client.stop(); started = false }

Try / catch

try {
  await client.stop()
} catch (e) {
  // ignore: client not running (never started, stopping, or already stopped)
}

Prevention

When it happens

Trigger: Calling client.stop() before start() ever completed (state is New/Starting); calling stop() twice (second call hits Stopping/Stopped state); stop() racing with a still-pending _start(); stop() after dispose already completed.

Common situations: Autocommands (VimLeave, BufUnload) firing stop on a never-started server; extensions stopping a client whose start is still awaiting the server handshake; double shutdown paths (coc shutdown + extension dispose both calling stop); watchdogs stopping clients during initialization timeouts.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/11105d836e2ed6e7. Report an issue: GitHub.