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
- Ensure fd 0 (stdin) is open before calling daemonize; do not close std fds in startup code
- If stdin may be closed, reopen /dev/null onto fd 0 first, then run daemonize
- 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
- Never close fds 0/1/2 in startup code before daemonize
- Reopen closed std fds to /dev/null before exec'ing the job
- Check seccomp/apparmor policies permit dup2 in hardened environments
- Log raw fds at daemonize entry when debugging EBADF
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
- failed to setsid: %w
- failed to open /dev/null: %w
- failed to daemonize: %w
- failed to create log directory: %w
- failed to open log file: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/77350840d807cd84.
Report an issue: GitHub.