wavetermdev/waveterm · warning

failed to dup2 stdin: %w

Error message

failed to dup2 stdin: %w

What it means

daemonize redirects stdin to /dev/null via unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd())). This error wraps the Dup2 errno. Dup2 over stdin almost never fails in normal conditions; failures indicate an invalid file descriptor or hitting the file-descriptor limit during exotic startup states.

Source

Thrown at pkg/jobmanager/jobmanager_unix.go:32

	"syscall"

	"github.com/wavetermdev/waveterm/pkg/wavebase"
	"golang.org/x/sys/unix"
)

func daemonize(clientId string, jobId string) error {
	_, err := unix.Setsid()
	if err != nil {
		return fmt.Errorf("failed to setsid: %w", err)
	}

	devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
	if err != nil {
		return fmt.Errorf("failed to open /dev/null: %w", err)
	}
	err = unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd()))
	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)
	}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ensure fd 0 (stdin) is open before calling daemonize; do not close std fds in startup code
  2. If stdin may be closed, reopen /dev/null onto fd 0 first, then run daemonize
  3. Inspect the wrapped errno: EBADF means invalid fd — check exec/fork handling; EPERM may indicate seccomp restrictions

Example fix

// before
err = unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd()))
if err != nil {
    return fmt.Errorf("failed to dup2 stdin: %w", err)
}
// after
if err := ensureFdOpen(0, os.O_RDWR); err != nil { // reopen /dev/null onto fd 0 if closed
    return err
}
if err := unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd())); err != nil {
    return fmt.Errorf("failed to dup2 stdin: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// ensure fd 0 is open before daemonize
if _, err := unix.FcntlInt(0, unix.F_GETFD, 0); err != nil {
    // reopen /dev/null onto fd 0
    f, _ := os.OpenFile("/dev/null", os.O_RDWR, 0)
    unix.Dup2(int(f.Fd()), 0)
}

Try / catch

if err := daemonize(clientId, jobId); err != nil {
    if strings.Contains(err.Error(), "failed to dup2 stdin") {
        log.Printf("stdin fd invalid at daemonize: %v", errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: unix.Dup2 fails with EBADF when os.Stdin's fd (0) is closed/invalid at daemonize time, or with EMFILE/other errno under fd-table corruption; also triggered when stdin was already closed by prior startup code before daemonize runs.

Common situations: Parent process closed fd 0 before exec'ing the job; stdin shutdown race during daemonization; resource limits or seccomp filters blocking dup2 in hardened environments.

Related errors


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