wavetermdev/waveterm · error
failed to setsid: %w
Error message
failed to setsid: %w
What it means
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.
Source
Thrown at pkg/jobmanager/jobmanager_unix.go:23
package jobmanager
import (
"fmt"
"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)
}View on GitHub (pinned to a4447c1563)
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
Example fix
// before
if _, err := unix.Setsid(); err != nil {
return fmt.Errorf("failed to setsid: %w", err)
}
// after
if _, err := unix.Setsid(); err != nil {
if errors.Is(err, unix.EPERM) {
// already a session leader; treat as detached
return nil
}
return fmt.Errorf("failed to setsid: %w", err)
} Defensive patterns
Strategy: fallback
Validate before calling
// in the child process, before daemonize: // guarantee this process is NOT a process-group leader // (fork/exec or re-exec a fresh child that runs daemonize as its first action)
Try / catch
if err := daemonize(clientId, jobId); err != nil {
if strings.Contains(err.Error(), "failed to setsid") {
// EPERM: already a session/group leader — either skip or re-exec a child
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to daemonize: %w
- failed to open /dev/null: %w
- failed to dup2 stdin: %w
- procinfo: process not found
- failed to run app: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/f60097c617bd30df.
Report an issue: GitHub.