neoclide/coc.nvim · error · Error

Client got disposed and can't be restarted.

Error message

Client got disposed and can't be restarted.

What it means

LanguageClient._start() throws this when the client has already been disposed (this._disposed is 'disposing' or 'disposed') and someone attempts to start it again. vscode-languageserver library clients are single-use: once stopped/disposed they cannot be restarted, and coc throws instead of silently failing.

Source

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

        }
      })
    })
  }

  public get started(): boolean {
    return this.$state != ClientState.Initial
  }

  public isRunning(): boolean {
    return this.$state === ClientState.Running
  }
  /**
   * @internal
   */

  public async _start(): Promise<void> {
    if (this._disposed === 'disposing' || this._disposed === 'disposed') {
      throw new Error(`Client got disposed and can't be restarted.`)
    }
    if (this.$state === ClientState.Stopping) {
      throw new Error(`Client is currently stopping. Can only restart a full stopped client`)
    }
    // We are already running or are in the process of getting up
    // to speed.
    if (this._onStart !== undefined) {
      return this._onStart
    }
    this._rootPath = this.resolveRootPath()

    const [promise, resolve, reject] = this.createOnStartPromise()
    this._onStart = promise

    this._diagnostics = undefined

    // When we start make all buffer handlers pending so that they
    // get added.

View on GitHub (pinned to 50e974d969)

Solutions

  1. Create a fresh LanguageClient instance instead of restarting the disposed one.
  2. Track client state before calling start: only start when _disposed is undefined and state is not running.
  3. Serialize start/stop calls (await stop fully, then construct a new client).
  4. Run :CocRestart, which rebuilds client instances rather than reusing disposed ones.

Example fix

// before
await client.stop()
await client.start() // throws: client disposed

// after
await client.stop()
client = createClient() // build a new instance
await client.start()
Defensive patterns

Strategy: try-catch

Validate before calling

// only start when not disposed (internal check pattern)
function canStart(client: LanguageClient): boolean {
  return (client as any)._disposed === undefined
}

Type guard

function isStartable(client: LanguageClient): boolean {
  const disposed = (client as any)._disposed
  return disposed !== 'disposing' && disposed !== 'disposed'
}

Try / catch

try {
  await client.start()
} catch (e) {
  if (String(e).includes('got disposed')) {
    client = createNewClient() // rebuild instead of restarting
    await client.start()
  }
}

Prevention

When it happens

Trigger: Calling client.start() (or restart that routes to _start) after client.stop()/dispose() completed or while dispose is in progress; coc's client manager restarting a server whose client instance was already disposed; calling start concurrently with a stop.

Common situations: Custom code or an extension holding a reference to a LanguageClient and calling start after stop; server shutdown triggered by :CocRestart while an async start was pending; race between workspace folder changes disposing clients and pending onDidChangeWorkspaceFolder handlers starting them.

Related errors


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