Skip to content

refreshDiagnostics() called synchronously after rebuild blocks event loop, preventing completion registration on Windows #1568

@zeroarst

Description

@zeroarst

What version of VS Code are you using?

WebStorm 2026.1 (JetBrains IDE, not VS Code — the language server is the same bundled binary)

What version of Tailwind CSS IntelliSense are you using?

v0.14.29 (bundled in WebStorm Tailwind CSS plugin 261.22158.274)

What version of Tailwind CSS are you using?

v3.4.3

What package manager are you using?

pnpm

What operating system are you using?

Windows 10

Tailwind CSS config file (v3)

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Reproduction URL

Any Windows project using an editor that has brief periods of not reading stdout (e.g. during internal write-actions/transactions).

Describe your issue

After the project rebuilds successfully and state.enabled = true is set, Tailwind CSS completions still do not appear. The server has built the class list correctly but completions are never registered with the editor.

Root cause: In the rebuild function (packages/tailwindcss-language-server/src/projects.ts), after state.enabled = true the code calls refreshDiagnostics() and updateProjectCapabilities() synchronously in the same tick:

// Simplified from current source:
state.enabled = true
refreshDiagnostics()    // called first
updateProjectCapabilities()

refreshDiagnostics() iterates all open documents and calls provideDiagnosticsForce() for each, which triggers getConfiguration(uri)connection.workspace.getConfiguration()synchronously writes a JSON-RPC request to stdout for every document.

On Windows, if the editor is momentarily not reading the stdout pipe (e.g. during an internal write-action or transaction), the pipe buffer fills up and process.stdout.write() blocks. This freezes the Node.js event loop entirely, preventing updateProjectCapabilities() from running and — critically — preventing updateTriggerCharacters() from ever receiving the editor's acknowledgement of the client/registerCapability response. Completion capabilities are therefore never registered.

Fix: Defer refreshDiagnostics() to a setImmediate() after updateProjectCapabilities(), so the capability registration handshake can complete before diagnostics are requested:

state.enabled = true
updateProjectCapabilities()
setImmediate(() => refreshDiagnostics())

Additionally, to avoid the pipe-fill problem entirely, refreshDiagnostics() itself should process documents one per event loop tick rather than all at once:

refreshDiagnostics() {
  const docs = Array.from(this.documentService.getAllDocuments())
  let i = 0
  const next = () => {
    if (i >= docs.length) return
    const doc = docs[i++]
    const project = this.getProject(doc)
    project
      ? project.provideDiagnosticsForce(doc)
      : this.connection.sendDiagnostics({ uri: doc.uri, diagnostics: [] })
    setImmediate(next)
  }
  setImmediate(next)
}

After these two changes, updateTriggerCharacters() receives its ack and completions are registered correctly.

Note: This may be less visible in VS Code because VS Code reads stdout more eagerly and rarely lets the pipe buffer fill. It is consistently reproducible with WebStorm on Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions