{"record":{"id":"f60097c617bd30df","repo":"wavetermdev/waveterm","slug":"failed-to-setsid-w","errorCode":null,"errorMessage":"failed to setsid: %w","messagePattern":"failed to setsid: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/jobmanager/jobmanager_unix.go","lineNumber":23,"sourceCode":"\npackage jobmanager\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"syscall\"\n\n\t\"github.com/wavetermdev/waveterm/pkg/wavebase\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc daemonize(clientId string, jobId string) error {\n\t_, err := unix.Setsid()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to setsid: %w\", err)\n\t}\n\n\tdevNull, err := os.OpenFile(\"/dev/null\", os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open /dev/null: %w\", err)\n\t}\n\terr = unix.Dup2(int(devNull.Fd()), int(os.Stdin.Fd()))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to dup2 stdin: %w\", err)\n\t}\n\tdevNull.Close()\n\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}","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/jobmanager/jobmanager_unix.go#L5-L41","documentation":"daemonize in jobmanager_unix.go calls unix.Setsid() to detach the job process into a new session, a standard daemonization step. This error wraps the raw errno from Setsid. Setsid only fails when the calling process is already a process group leader (EPERM), which happens when daemonize is invoked from the wrong process or too late in fork/exec ordering.","triggerScenarios":"unix.Setsid() returns EPERM because the calling process is already a process-group leader — e.g. daemonize invoked directly from the shell-facing parent process instead of a freshly forked child, or after setpgid was already applied.","commonSituations":"Calling the daemonize path from an interactive job process that the terminal already made a group leader; job control (SIGTTOU/setpgid) interference; embedding the job manager in a process that already called Setsid.","solutions":["Ensure daemonize runs in a freshly forked child (e.g. via syscall.ForkExec/Reexec) so it is not a group leader","Remove any earlier setpgid/Setpgid call on the process before daemonize","Treat EPERM as 'already detached' if double-daemonization is possible, and continue"],"exampleFix":"// before\nif _, err := unix.Setsid(); err != nil {\n    return fmt.Errorf(\"failed to setsid: %w\", err)\n}\n// after\nif _, err := unix.Setsid(); err != nil {\n    if errors.Is(err, unix.EPERM) {\n        // already a session leader; treat as detached\n        return nil\n    }\n    return fmt.Errorf(\"failed to setsid: %w\", err)\n}","handlingStrategy":"fallback","validationCode":"// in the child process, before daemonize:\n// guarantee this process is NOT a process-group leader\n// (fork/exec or re-exec a fresh child that runs daemonize as its first action)","typeGuard":null,"tryCatchPattern":"if err := daemonize(clientId, jobId); err != nil {\n    if strings.Contains(err.Error(), \"failed to setsid\") {\n        // EPERM: already a session/group leader — either skip or re-exec a child\n    }\n    return err\n}","preventionTips":["Run daemonize only in a freshly forked/exec'd child process","Do not call setpgid or start the job as a group leader before daemonize","Treat EPERM from Setsid as 'already detached' when idempotent behavior is acceptable","Log the pid/pgid at daemonize entry to debug session-leader conflicts"],"tags":["go","unix","daemonize","process","eperm"],"backgroundTag":"setsid-eperm","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}