microsoft/typescript-go · error

failed to accept connection: %w

Error message

failed to accept connection: %w

What it means

StdioServer.Run could not accept the client connection: transport.Accept() returned an error. With PipePath set this is the net.Listener's Accept failing (listener closed, socket unlinked from the filesystem, or fd exhaustion); with the default stdio transport it is effectively unreachable on the first call (Accept wraps stdin/stdout once and only returns io.EOF on a second call). The wrapped error carries the OS cause.

Source

Thrown at internal/api/server.go:101

		Logger:        nil, // TODO: Add logging support
		FS:            fs,
		Options: &project.SessionOptions{
			CurrentDirectory:   s.options.Cwd,
			DefaultLibraryPath: s.options.DefaultLibraryPath,
			PositionEncoding:   lsproto.PositionEncodingKindUTF8,
			LoggingEnabled:     false,
		},
	})

	session := NewSession(projectSession, &SessionOptions{
		UseBinaryResponses: !s.options.Async, // Only msgpack uses binary responses
	})
	defer session.Close()

	// Accept connection from transport
	rwc, err := transport.Accept()
	if err != nil {
		return fmt.Errorf("failed to accept connection: %w", err)
	}

	// Create protocol and connection based on async mode
	var conn Conn
	if s.options.Async {
		protocol := NewJSONRPCProtocol(rwc)
		asyncConn := NewAsyncConnWithProtocol(rwc, protocol, session)
		asyncConn.SetCollectTiming(s.options.CollectTiming)
		conn = asyncConn
	} else {
		protocol := NewMessagePackProtocol(rwc)
		syncConn := NewSyncConn(rwc, protocol, session)
		syncConn.SetCollectTiming(s.options.CollectTiming)
		conn = syncConn
	}

	// If callbacks are enabled, set the connection on the FS
	if callbackFS != nil {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Check the wrapped cause: net.ErrClosed means intentional shutdown (treat as benign), EMFILE means raise the fd limit, 'no such file or directory' means the socket was unlinked - recreate the transport
  2. Ensure the socket path lives in a directory nothing else cleans while the server runs
  3. Create a fresh StdioServer per connection; Run is one-shot for the stdio transport
  4. Raise ulimit -n / handle limits if fd exhaustion recurs

Example fix

// before
if err := srv.Run(ctx); err != nil {
    log.Fatalf("server failed: %v", err) // accept error crashes the host
}

// after
if err := srv.Run(ctx); err != nil {
    if errors.Is(err, net.ErrClosed) || errors.Is(err, context.Canceled) {
        return // deliberate shutdown
    }
    log.Printf("server failed: %v", err)
}
Defensive patterns

Strategy: retry

Try / catch

err := srv.Run(ctx)
switch {
case err == nil, errors.Is(err, net.ErrClosed), errors.Is(err, context.Canceled):
    // deliberate shutdown - nothing to retry
case errors.Is(err, syscall.ECONNABORTED) || errors.Is(err, syscall.EMFILE):
    // transient: brief backoff, then rebuild transport + retry once
    time.Sleep(100 * time.Millisecond)
    err = srv.Run(ctx)
    if err != nil { log.Printf("accept retry failed: %v", err) }
default:
    log.Printf("accept failed: %v", err) // e.g. socket unlinked - recreate transport
}

Prevention

When it happens

Trigger: The unix socket file was deleted out from under the listener (another process cleans the temp dir) so Accept fails; the process hit its file-descriptor limit (EMFILE) when the client connected; the listener was closed concurrently (e.g. ctx-driven teardown closing the transport) before the client connected; calling Run twice on the same StdioServer so the second Accept returns io.EOF.

Common situations: Temp-dir cleaners (systemd-tmpfiles, CI workspace wipes) removing the socket path while the server waits; heavily loaded hosts exhausting fds; a host supervisor racing shutdown with the first client connect; test harnesses that reuse a server instance across connections.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/561a25249209e84e. Report an issue: GitHub.