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
- Create a fresh LanguageClient instance instead of restarting the disposed one.
- Track client state before calling start: only start when _disposed is undefined and state is not running.
- Serialize start/stop calls (await stop fully, then construct a new client).
- 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
- Treat LanguageClient as single-use: never call start() after stop()/dispose().
- Create a new client instance for every restart cycle.
- Serialize start/stop calls; never fire start while a stop is pending.
- Use :CocRestart rather than manually restarting held client references.
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
- Client is currently stopping. Can only restart a full stoppe
- Previous start failed. Can't restart server.
- Client is not running and can't be stopped. Its current stat
- Starting server failed
- ${uri} changed before apply edit
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/78deeb3480629741.
Report an issue: GitHub.