wavetermdev/waveterm · critical

failed to listen: %v

Error message

failed to listen: %v

What it means

ClientImpl.listenAndServe opens a TCP listener for the client's HTTP endpoint (with the given mux). If net.Listen fails — address already bound, permission denied, or invalid address — it aborts with this wrapped error.

Source

Thrown at tsunami/engine/clientimpl.go:237

		ManifestFile: c.ManifestFileBytes,
	})

	// Determine listen address from environment variable or use default
	listenAddr := os.Getenv(TsunamiListenAddrEnvVar)
	if listenAddr == "" {
		listenAddr = DefaultListenAddr
	}

	// Create server and listen on specified address
	server := &http.Server{
		Addr:    listenAddr,
		Handler: mux,
	}

	// Start listening
	listener, err := net.Listen("tcp", listenAddr)
	if err != nil {
		return fmt.Errorf("failed to listen: %v", err)
	}

	// Log the address we're listening on
	port := listener.Addr().(*net.TCPAddr).Port
	log.Printf("[tsunami] listening at http://localhost:%d", port)

	// Serve in a goroutine so we don't block
	go func() {
		if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
			log.Printf("HTTP server error: %v", err)
		}
	}()

	// Wait for context cancellation and shutdown server gracefully
	go func() {
		<-ctx.Done()
		log.Printf("Context canceled, shutting down server...")
		if err := server.Shutdown(context.Background()); err != nil {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Stop the process already bound to the port (lsof -i :PORT / kill) or pick a free port
  2. Use port 0 to let the OS pick a free port (the code logs the actual bound port)
  3. Run with sufficient privileges if binding a privileged port, or choose a high port
  4. Validate listenAddr syntax (host:port) in configuration

Example fix

// before
listenAddr := "localhost:9310" // already in use
// after
listener, err := net.Listen("tcp", "localhost:0") // OS-assigned free port
Defensive patterns

Strategy: retry

Validate before calling

func portFree(addr string) bool {
    l, err := net.Listen("tcp", addr)
    if err != nil { return false }
    l.Close()
    return true
}

Try / catch

if err := runMainE(...); err != nil {
    if strings.Contains(err.Error(), "failed to listen") {
        time.Sleep(500 * time.Millisecond) // brief backoff, then retry or pick port 0
    }
}

Prevention

When it happens

Trigger: net.Listen("tcp", listenAddr) returning an error: another instance already listening on the port (EADDRINUSE), privileged port without root, malformed listen address, or IPv6 unavailability.

Common situations: Starting a second tsunami client while the first still runs; leftover process holding the port after a crash; Docker/CI port conflicts; running on port <1024 as non-root.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/4cb225e6d8ae9480. Report an issue: GitHub.