wavetermdev/waveterm · error

failed to open /dev/null: %w

Error message

failed to open /dev/null: %w

What it means

After Setsid, daemonize opens /dev/null read-write and duplicates it over stdin to detach the daemon from the controlling terminal. This error wraps the os.OpenFile("/dev/null", O_RDWR, 0) failure. On Linux /dev/null is always present, so failures indicate an unusual or broken filesystem/device environment.

Source

Thrown at pkg/jobmanager/jobmanager_unix.go:28

	"log"
	"os"
	"os/signal"
	"path/filepath"
	"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)
	}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify /dev/null exists and is a character device: ls -l /dev/null (expect crw-rw-rw-)
  2. Recreate it if broken: mknod /dev/null c 1 3 && chmod 666 /dev/null
  3. Mount /dev (or run with proper device cgroup allowances) in containers/chroots
  4. Check that the process's uid can open the node read-write before daemonizing

Example fix

// before
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
    return fmt.Errorf("failed to open /dev/null: %w", err)
}
// after
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
    if fi, serr := os.Stat("/dev/null"); serr != nil || !fi.Mode().IsRegular() == false {
        return fmt.Errorf("/dev/null is not a proper device, fix container /dev: %w", err)
    }
    return fmt.Errorf("failed to open /dev/null: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat("/dev/null"); err != nil {
    return fmt.Errorf("/dev/null missing: mount /dev")
} else if fi.Mode()&os.ModeCharDevice == 0 {
    return fmt.Errorf("/dev/null is not a character device")
}

Try / catch

if err := daemonize(clientId, jobId); err != nil {
    if strings.Contains(err.Error(), "failed to open /dev/null") {
        log.Printf("container/chroot /dev broken: %v", errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: os.OpenFile("/dev/null", os.O_RDWR, 0) fails: /dev/null missing or is not a character device, /dev not mounted (chroot/minimal container), or permission denied on the device node.

Common situations: Minimal containers or chroots without /dev mounted; /dev/null accidentally deleted or replaced by a regular file with wrong perms; extremely low fd/file permissions after privilege drop; broken device nodes in exotic sandbox environments.

Related errors


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