neoclide/coc.nvim · error · Error

Process created without stdio streams

Error message

Process created without stdio streams

What it means

When spawning the language server as a child process with stdio transport, the client asserts stdin/stdout/stderr are all non-null. If Node returned null streams (process failed to get pipes), the process is SIGKILLed and this error is thrown instead of hanging on a dead transport.

Source

Thrown at src/language-client/index.ts:266

  protected createMessageTransports(encoding: string): Promise<MessageTransports> {

    function getEnvironment(env: any, fork: boolean): any {
      if (!env && !fork) {
        return undefined
      }
      let result: any = Object.create(null)
      Object.keys(process.env).forEach(key => result[key] = process.env[key])
      if (env) {
        Object.keys(env).forEach(key => result[key] = env[key])
      }
      return result
    }

    function assertStdio(process: ChildProcess): asserts process is ChildProcessWithoutNullStreams {
      if (process.stdin === null || process.stdout === null || process.stderr === null) {
        process.kill('SIGKILL')
        throw new Error('Process created without stdio streams')
      }
    }

    function logMessage(kind: string, data: string, outputChannel: OutputChannel): void {
      let msg = `[${kind} - ${currentTimeStamp()}] ${data}`
      outputChannel.appendLine(msg)
    }

    function pipeStdoutToLogOutputChannel(input: stream.Readable, outputChannel: OutputChannel) {
      readline.createInterface({
        input,
        crlfDelay: Infinity,
        terminal: false,
        historySize: 0,
      }).on('line', data => logMessage('Stdout', data, outputChannel))
    }

    function pipeStderrToLogOutputChannel(input: stream.Readable, outputChannel: OutputChannel) {

View on GitHub (pinned to 50e974d969)

Solutions

  1. Do not override stdio in child_process spawn options for the server
  2. Verify the server command is a real executable that keeps stdio attached
  3. Remove shell wrappers (cmd /c, .bat indirection) or ensure they forward stdio
  4. Check Node version; spawn with the correct ChildProcessSpawnOptions

Example fix

// before
const serverOptions: ServerOptions = () => spawn(cmd, args, { stdio: 'ignore' })
// after
const serverOptions: ServerOptions = () => spawn(cmd, args) // stdio pipes created by default
Defensive patterns

Strategy: validation

Validate before calling

// Before starting the server, verify the command exists and options don't override stdio
if (!opts.stdio && !commandExists(serverCommand)) {
  throw new Error(`Server command not found: ${serverCommand}`)
}

Type guard

function hasNullStreams(p: ChildProcess): boolean {
  return p.stdin === null || p.stdout === null || p.stderr === null
}

Try / catch

try {
  const client = new LanguageClient(id, name, serverOptions, clientOptions)
  await client.start()
} catch (e) {
  if (String(e.message).includes('without stdio streams')) {
    console.error('Server spawn failed to attach stdio; check command/options')
  } else { throw e }
}

Prevention

When it happens

Trigger: server module launched with a command that Node could not attach stdio pipes to (e.g. executable options overriding stdio, spawn environment issues, or a command that immediately detaches).

Common situations: Server options inadvertently setting stdio: 'ignore'; spawning GUI wrappers that don't inherit stdio; resource limits preventing pipe creation; wrong executable path combined with shell wrappers.

Related errors


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