{"record":{"id":"dec1df4549be27c2","repo":"gastownhall/beads","slug":"sending-sigterm-to-pid-d-w","errorCode":null,"errorMessage":"sending SIGTERM to PID %d: %w","messagePattern":"sending SIGTERM to PID (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/doltserver/doltserver_unix.go","lineNumber":146,"sourceCode":"// Uses signal 0 which doesn't send a signal but checks process existence.\nfunc isProcessAlive(pid int) bool {\n\tprocess, err := os.FindProcess(pid)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn process.Signal(syscall.Signal(0)) == nil\n}\n\n// gracefulStop sends SIGTERM, waits for the process to exit, then SIGKILL if needed.\n// Used by reclaimPort and StopWithForce where data has already been flushed.\nfunc gracefulStop(pid int, timeout time.Duration) error {\n\tprocess, err := os.FindProcess(pid)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"finding process %d: %w\", pid, err)\n\t}\n\n\tif err := process.Signal(syscall.SIGTERM); err != nil {\n\t\treturn fmt.Errorf(\"sending SIGTERM to PID %d: %w\", pid, err)\n\t}\n\n\t// Poll for exit\n\tdeadline := time.Now().Add(timeout)\n\tfor time.Now().Before(deadline) {\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tif process.Signal(syscall.Signal(0)) != nil {\n\t\t\treturn nil // exited\n\t\t}\n\t}\n\n\t// Still running — force kill\n\t_ = process.Signal(syscall.SIGKILL)\n\ttime.Sleep(100 * time.Millisecond)\n\treturn nil\n}\n","sourceCodeStart":128,"sourceCodeEnd":163,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/doltserver/doltserver_unix.go#L128-L163","documentation":"gracefulStop wraps process.Signal(syscall.SIGTERM) failures as \"sending SIGTERM to PID %d: %w\". On Unix, Signal returns os.ErrProcessDone if the target already exited; other errors mean permission problems or the process vanished. This library throws it when it cannot deliver the polite termination signal to the dolt-server process during reclaimPort or StopWithForce (after data was already flushed).","triggerScenarios":"gracefulStop is called and process.Signal(SIGTERM) errors: target process already exited (os.ErrProcessDone), the caller lacks permission to signal the process (different user), or the PID no longer exists.","commonSituations":"The dolt server crashed between the FindProcess and Signal calls; bd runs as a different user than the stale server process (e.g. after sudo/user change); containerized environments where the PID is outside the namespace.","solutions":["Treat os.ErrProcessDone (errors.Is) as success and proceed — the server is already stopped.","Check process ownership/permissions: run bd as the same user that started the dolt server (ps -o user= -p <pid>).","If the process is a zombie/defunct, reap or ignore it and continue; the port should be releasable.","Fall through to SIGKILL path (StopWithForce already flushed data) or continue startup if the port is free."],"exampleFix":"// before\nif err := process.Signal(syscall.SIGTERM); err != nil {\n    return fmt.Errorf(\"sending SIGTERM to PID %d: %w\", pid, err)\n}\n// after\nif err := process.Signal(syscall.SIGTERM); err != nil {\n    if errors.Is(err, os.ErrProcessDone) {\n        return nil // already stopped\n    }\n    return fmt.Errorf(\"sending SIGTERM to PID %d: %w\", pid, err)\n}","handlingStrategy":"try-catch","validationCode":"p, err := os.FindProcess(pid)\nif err == nil {\n    if err := p.Signal(syscall.Signal(0)); err != nil {\n        // cannot signal: wrong user or process gone\n    }\n}","typeGuard":"func canSignal(pid int) bool {\n    p, err := os.FindProcess(pid)\n    if err != nil { return false }\n    return errors.Is(p.Signal(syscall.Signal(0)), syscall.EPERM) == false\n}","tryCatchPattern":"if err := process.Signal(syscall.SIGTERM); err != nil {\n    if errors.Is(err, os.ErrProcessDone) {\n        return nil\n    }\n    var errno syscall.Errno\n    if errors.As(err, &errno) && errno == syscall.EPERM {\n        return fmt.Errorf(\"insufficient permissions to signal pid %d (wrong user?)\", pid)\n    }\n    return err\n}","preventionTips":["Check process ownership (ps -o user= -p <pid>) matches the bd user","Use errors.Is(err, os.ErrProcessDone) to treat exits as success","Avoid stale PIDs: re-read metadata before signaling","In containers, signal only PIDs inside the same namespace"],"tags":["process","signals","sigterm","permissions","graceful-shutdown"],"backgroundTag":"signal-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}