{"record":{"id":"cdbb8b33a6fba202","repo":"wavetermdev/waveterm","slug":"failed-to-dup2-stderr-w","errorCode":null,"errorMessage":"failed to dup2 stderr: %w","messagePattern":"failed to dup2 stderr: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/jobmanager/jobmanager_unix.go","lineNumber":53,"sourceCode":"\n\tlogPath := wavebase.GetRemoteJobFilePath(jobId, \"log\")\n\tlogDir := filepath.Dir(logPath)\n\terr = os.MkdirAll(logDir, 0700)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create log directory: %w\", err)\n\t}\n\n\tlogFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open log file: %w\", err)\n\t}\n\terr = unix.Dup2(int(logFile.Fd()), int(os.Stdout.Fd()))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to dup2 stdout: %w\", err)\n\t}\n\terr = unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to dup2 stderr: %w\", err)\n\t}\n\n\tlog.SetOutput(logFile)\n\tlog.Printf(\"job manager daemonized, logging to %s\\n\", logPath)\n\tlog.Printf(\"job owner clientid: %s\\n\", clientId)\n\n\tsignal.Ignore(syscall.SIGHUP)\n\n\treturn nil\n}\n","sourceCodeStart":35,"sourceCodeEnd":64,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/jobmanager/jobmanager_unix.go#L35-L64","documentation":"This error is returned by daemonize when unix.Dup2 fails while redirecting stderr (fd 2) to the log file, after stdout redirection already succeeded. It wraps the underlying syscall errno. Same class of OS-level descriptor problems as the stdout variant.","triggerScenarios":"daemonize() succeeds opening the log file and redirecting stdout, but unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd())) returns an error — typically because fd 2 is closed/invalid or fds are exhausted.","commonSituations":"Process started with stderr closed (e.g. `cmd 2>&-`), supervisor frameworks that close fd 2, or rlimit NOFILE exhaustion.","solutions":["Ensure fd 2 is open before daemonizing (open /dev/null onto fd 2 if needed).","Raise the file descriptor limit (ulimit -n / systemd LimitNOFILE).","Inspect the wrapped errno from the %w error to pinpoint the syscall failure."],"exampleFix":"// before\nerr = unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))\nif err != nil {\n    return fmt.Errorf(\"failed to dup2 stderr: %w\", err)\n}\n// after\n// guarantee fd 2 is valid before dup2\nif _, err := unix.FcntlInt(2, unix.F_GETFD, 0); err != nil {\n    devnull, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0)\n    unix.Dup2(int(devnull.Fd()), 2)\n}\nerr = unix.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))","handlingStrategy":"validation","validationCode":"if _, err := unix.FcntlInt(2, unix.F_GETFD, 0); err != nil {\n    devnull, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0)\n    unix.Dup2(int(devnull.Fd()), 2)\n}","typeGuard":null,"tryCatchPattern":"if err := daemonize(clientId, jobId); err != nil {\n    if strings.Contains(err.Error(), \"dup2 stderr\") {\n        log.Printf(\"stderr redirect failed: %v\", err)\n    }\n    return err\n}","preventionTips":["Never launch with `2>&-`; keep stderr open","Check ulimit -n when opening many files before daemonizing","Test daemonization under your actual supervisor (systemd, docker)"],"tags":["unix","file-descriptor","daemonization","syscall"],"backgroundTag":"dup2-failed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}