wavetermdev/waveterm · error

failed to dup2 stdout: %w

Error message

failed to dup2 stdout: %w

What it means

This error is returned by the Unix daemonize function in the job manager when unix.Dup2 fails to redirect the process's stdout file descriptor to the opened log file. The job manager redirects stdout and stderr to a log file so daemonized output is captured. Dup2 failing here is an OS-level file descriptor error (EBADF, EMFILE, etc.).

Source

Thrown at pkg/jobmanager/jobmanager_unix.go:49

	if err != nil {
		return fmt.Errorf("failed to dup2 stdin: %w", err)
	}
	devNull.Close()

	logPath := wavebase.GetRemoteJobFilePath(jobId, "log")
	logDir := filepath.Dir(logPath)
	err = os.MkdirAll(logDir, 0700)
	if err != nil {
		return fmt.Errorf("failed to create log directory: %w", err)
	}

	logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
	if err != nil {
		return fmt.Errorf("failed to open log file: %w", err)
	}
	err = unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
	if err != nil {
		return fmt.Errorf("failed to dup2 stdout: %w", err)
	}
	err = unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
	if err != nil {
		return fmt.Errorf("failed to dup2 stderr: %w", err)
	}

	log.SetOutput(logFile)
	log.Printf("job manager daemonized, logging to %s\n", logPath)
	log.Printf("job owner clientid: %s\n", clientId)

	signal.Ignore(syscall.SIGHUP)

	return nil
}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ensure file descriptors 0/1/2 are open before daemonizing (open /dev/null or a pipe for them if a supervisor closed them).
  2. Check ulimit -n / rlimit NOFILE for fd exhaustion and raise it.
  3. Retry daemonize after fixing the environment; check the wrapped %w error for the exact syscall errno.

Example fix

// before
err = unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
// after
// ensure stdout exists even when the process was launched with fd 1 closed
if os.Stdout.Fd() < 0 {
    devnull, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0)
    unix.Dup2(int(devnull.Fd()), 1)
}
err = unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))
Defensive patterns

Strategy: validation

Validate before calling

for fd := 0; fd <= 2; fd++ {
    if _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); err != nil {
        devnull, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0)
        unix.Dup2(int(devnull.Fd()), fd)
    }
}

Try / catch

if err := daemonize(clientId, jobId); err != nil {
    var errno unix.Errno
    if errors.As(err, &errno) { log.Printf("dup2 errno: %v", errno) }
    return fmt.Errorf("daemonize failed: %w", err)
}

Prevention

When it happens

Trigger: daemonize() is called and unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd())) returns a non-nil error — e.g. the process has no valid stdout descriptor (detached/no TTY with fd 0-2 closed) or fd exhaustion.

Common situations: Launching the job manager from a supervisor that closed file descriptors, running under environments where stdout is closed, or hitting the open file descriptor limit (ulimit -n) after opening the log file.

Related errors


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